Преглед изворни кода

手术新增等级和主刀医生显示,优化电子病历编辑判断

xiaochan пре 2 година
родитељ
комит
0660779846

+ 1 - 1
.env.dev

@@ -2,4 +2,4 @@ ENV = 'dev'
 
 VITE_BASE_URL = 'http://172.16.30.61:9201'
 VITE_SOCKET_URL = 'ws://172.16.30.61:9201/websocket/'
-VITE_EMR_CONTROL_URL = 'http://172.16.30.67:9227'
+VITE_EMR_CONTROL_URL = 'http://172.16.30.61:9227'

+ 1 - 1
.env.production

@@ -2,4 +2,4 @@ ENV = 'production'
 
 VITE_BASE_URL = 'http://172.16.32.160:8706'
 VITE_SOCKET_URL = 'ws://172.16.32.160:8706/websocket/'
-VITE_EMR_CONTROL_URL = 'http://172.16.30.160:8706'
+VITE_EMR_CONTROL_URL = 'http://172.16.30.160:9227'

+ 25 - 0
src/api/emr-control/connotation-quality-control.js

@@ -0,0 +1,25 @@
+import service from "./request";
+
+export function save(data) {
+    return service({
+        url: '/save',
+        method: 'post',
+        data
+    })
+}
+
+export function createAnEdit(data) {
+    return service({
+        url: '/timeLimitConfig/createAnEdit',
+        method: 'post',
+        data
+    })
+}
+
+export function reqUnlock(data) {
+    return service({
+        url: '/timeLimitConfig/reqUnlock',
+        method: 'post',
+        data
+    })
+}

+ 84 - 0
src/api/emr-control/request.ts

@@ -0,0 +1,84 @@
+import axios from 'axios'
+// @ts-ignore
+import {ElMessage, ElMessageBox} from "element-plus";
+// @ts-ignore
+import store from '@/store'
+
+
+const service = axios.create({
+    // @ts-ignore
+    baseURL: import.meta.env.VITE_EMR_CONTROL_URL,
+    withCredentials: true,
+    timeout: 0,
+})
+
+
+service.interceptors.request.use(
+    // @ts-ignore
+    (config) => {
+        if (store.getters['user/token']) {
+            config.headers['token'] = store.state.user.token
+        }
+        return config
+    },
+    (error) => {
+        return Promise.reject(error)
+    }
+)
+
+service.interceptors.response.use((response) => {
+    if (response.data.code === 200 || response.data.code === 0) {
+        return response.data.data
+    }
+    if (response.data.code === 201) {
+        ElMessage({
+            type: 'success',
+            duration: 2800,
+            dangerouslyUseHTMLString: true,
+            message: response.data.message,
+            showClose: true,
+        })
+        return response.data.data
+    }
+    if (response.data.code === 5000) {
+        ElMessage({
+            type: 'error',
+            duration: 2800,
+            message: response.data.message,
+            showClose: true,
+        })
+    }
+
+    if (response.data.code === 5001) {
+        ElMessageBox.alert(response.data.message, '提示', {
+            type: 'warning',
+            confirmButtonText: '确定',
+            showClose: false,
+        }).then(() => {
+        }).catch(() => {
+        })
+    }
+
+    if (response.data.code === 6001) {
+        ElMessage({
+            message: response.data.message,
+            type: 'error',
+            duration: 2500,
+            showClose: true,
+            grouping: true,
+        })
+        return {
+            error: true,
+            data: response.data.data
+        }
+    }
+
+
+    return Promise.reject(response.data.message || '服务器内部错误')
+}, (error) => {
+    // 超出 2xx 范围的状态码都会触发该函数。
+    // 对响应错误做点什么
+    return Promise.reject(error);
+});
+
+export default service

+ 0 - 7
src/api/zhu-yuan-yi-sheng/connotation-quality-control.js

@@ -1,7 +0,0 @@
-import axios from "axios";
-
-let url = import.meta.env.VITE_EMR_CONTROL_URL
-
-export function save(data) {
-    return axios.post(url + 'save', data)
-}

+ 18 - 0
src/api/zhu-yuan-yi-sheng/shou-shu-shen-qing.js

@@ -48,6 +48,15 @@ export function obtainSurgicalItems(name, type) {
     })
 }
 
+export function getDoctorByOpCode(opCode) {
+    return request({
+        url: url + 'getDoctorByOpCode',
+        method: 'get',
+        params: {opCode}
+    })
+}
+
+
 export function huoQuShouShuBuWei(name) {
     return request({
         url: url + 'huoQuShouShuBuWei',
@@ -64,6 +73,15 @@ export function xinZengShouShuShenQing(data) {
     })
 }
 
+export function preoperativeDiscussion(patNo, times, count) {
+    return request({
+        url: url + 'preoperativeDiscussion',
+        method: 'get',
+        params: {patNo, times, count}
+    })
+}
+
+
 export function shanChuShouShu(recordId) {
     return request({
         url: url + 'shanChuShouShu',

+ 4 - 1
src/components/xiao-chan/combo-grid/XcComboGrid.vue

@@ -55,6 +55,8 @@
 <script setup name='XcComboGrid'>
 import {debounce} from "@/utils/debounce";
 
+
+
 const props = defineProps({
   modelValue: {
     type: [String, Object],
@@ -127,7 +129,6 @@ const queryData = debounce((value) => {
   emit('input', value)
   tableConfig.value.isLoad = true
   if (props.queryDataFunc === null) return
-
   try {
     props.queryDataFunc(value).then((res) => {
       tableConfig.value.isShow = true
@@ -323,6 +324,7 @@ const isObj = typeof props.modelValue === 'object'
 onMounted(() => {
   rowAddClass()
   if (isObj) {
+
     watch(() => props.modelValue[props.code], () => {
       inputData.value = props.modelValue[props.name]
       highlightRow()
@@ -333,6 +335,7 @@ onMounted(() => {
       highlightRow()
     }, {immediate: true, deep: true})
   }
+
   if (props.data !== null) {
     watch(() => props.data.length, () => {
       if (isFocus.value) {

+ 169 - 0
src/components/xiao-chan/combo-grid/XcComboGridV2.vue

@@ -0,0 +1,169 @@
+<script setup lang="ts">
+import {computed, defineProps, onMounted, ref, watch} from "vue";
+import XEUtils from 'xe-utils'
+import {onClickOutside} from '@vueuse/core'
+
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Object],
+  },
+  queryDataFunc: {
+    type: Function,
+    default: null
+  },
+  data: {
+    type: Array,
+    twoWayBinding: true,
+    default: null
+  },
+  code: String,
+  name: String,
+  tableHeader: {
+    type: Array,
+    default: [
+      {title: '编码', field: 'code', width: 120},
+      {title: '名称', field: 'name', width: 220},
+    ]
+  },
+})
+
+const emits = defineEmits(['input', 'rowClick', 'clear', 'focus', 'blur', 'update:modelValue', 'update:data'])
+
+const isObj = typeof props.modelValue === 'object'
+const tempData = ref([])
+const isShow = ref(false)
+const divRef = ref()
+const inputRef = ref()
+
+const states = ref({
+  isFocus: false
+})
+
+
+const computedData = computed(() => {
+  return props.data === null ? tempData.value : props.data
+})
+
+const inputData = ref('')
+
+const change = XEUtils.debounce(async (value) => {
+  emits('input', value)
+  if (props.queryDataFunc === null) return
+  try {
+    let res = await props.queryDataFunc(value)
+    if (typeof res === 'undefined') {
+      return
+    }
+    if (props.data === null) {
+      tempData.value = res
+    }
+  } catch {
+  }
+}, 500)
+
+const rowClick = (...val) => {
+  console.log('row', ...val)
+}
+
+
+const rowClassName = (...val) => {
+  console.log('rowClassName', ...val)
+}
+
+
+const handleFocus = () => {
+  states.value.isFocus = true
+  isShow.value = true
+}
+
+const handleBlur = () => {
+  states.value.isFocus = false
+}
+
+const divClick = () => {
+  console.log('div点击事件')
+}
+
+watch(() => isShow.value, () => {
+  console.log('watch', isShow.value)
+})
+
+onMounted(() => {
+  if (isObj) {
+    watch(() => props.modelValue[props.code], () => {
+      inputData.value = props.modelValue[props.name]
+    }, {immediate: true, deep: true})
+  } else {
+    watch(() => props.modelValue, () => {
+      inputData.value = <string>props.modelValue
+    }, {immediate: true, deep: true})
+  }
+  onClickOutside(divRef, (event) => {
+    if (event.target.id === inputRef.value.input.id) {
+      isShow.value = true
+    } else {
+      isShow.value = false
+    }
+    console.log(isShow.value)
+
+  })
+
+})
+
+</script>
+
+<template>
+  <el-tooltip placement="bottom"
+              trigger="click"
+              popper-class="popover_padding_zero"
+              :visible="isShow"
+              effect="light"
+              pure
+              :fallback-placements="['bottom-start', 'top-start', 'right', 'left']"
+              :teleported="true"
+              :width="0">
+    <template #default>
+      <el-input
+          style="width: 120px;"
+          @focus="handleFocus"
+          @blur="handleBlur"
+          ref="inputRef"
+          @input="change"
+          v-model="inputData"
+      />
+    </template>
+    <template #content>
+      <div ref="divRef" style="padding: 5px">
+        <vxe-table
+            :height="200"
+            border
+            :scroll-x="{gt: 20}"
+            :scroll-y="{gt: 50,enabled: true}"
+            :column-config="{resizable: true}"
+            :row-config="{ height: 24}"
+            class="vxe-padding_zero vxe-header-max_content hl-style vxe-scroll_15"
+            header-row-class-name="padding_zero "
+            :row-class-name="rowClassName"
+            @cell-click="rowClick"
+            show-header-overflow
+            show-overflow
+            :data="computedData">
+
+          <vxe-column v-for="item in tableHeader"
+                      :title="item.title"
+                      :field="item.field"
+                      :width="item.width"/>
+          <slot/>
+        </vxe-table>
+      </div>
+    </template>
+
+  </el-tooltip>
+</template>
+
+<style lang="scss">
+.popover_padding_zero {
+  padding: 0 !important;
+}
+</style>

+ 2 - 1
src/components/xiao-chan/select-v3/XcSelectV3.vue

@@ -158,7 +158,7 @@ onClickOutside(selectRef, () => {
 })
 
 const selectInputRef = ref(null)
-const zIndex = ref(useZIndex().currentZIndex)
+const zIndex = ref(0)
 const boxClick = async () => {
   if (props.disabled) {
     return
@@ -438,6 +438,7 @@ onMounted(() => {
       showOptions = true
     })
   })
+  zIndex.value = useZIndex().currentZIndex
   if (!!useSlots().default) {
     useSlots().default().forEach((item) => {
       tableHeader.push(item.props)

+ 14 - 7
src/components/xiao-chan/xc-el-option/XcElOption.vue

@@ -1,11 +1,16 @@
 <script setup lang="ts" name='XcElOption'>
 import {defineProps} from 'vue'
 
-const {data} = defineProps({
-  data: Array<{
-    code: string,
-    name: string
-  }>
+const {data, value, label} = defineProps({
+  data: Array<{ code: string, name: string }>,
+  value: {
+    type: String,
+    default: 'code'
+  },
+  label: {
+    type: String,
+    default: 'name'
+  },
 })
 
 
@@ -14,8 +19,10 @@ const {data} = defineProps({
 <template>
   <el-option v-for="item in data"
              :key="item.code"
-             :value="item.code"
-             :label="item.name"/>
+             :value="item[value]"
+             :label="item[label]">
+    <slot/>
+  </el-option>
 </template>
 
 <style scoped lang="scss">

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

@@ -72,8 +72,11 @@ import {getAllWards} from "@/api/zhu-yuan-yi-sheng/resident-doctor";
 import RightClickMenu from "@/components/menu-item/RightClickMenu.vue";
 import {xcMessage} from "@/utils/xiaochan-element-plus";
 import store from "@/store";
-import {ElIcon} from "element-plus";
+import {ElIcon, ElMessageBox} from "element-plus";
 import {Folder, Document, Open, Sort, View} from "@element-plus/icons-vue";
+import {isDev} from "@/utils/public";
+import XEUtils from "xe-utils";
+import {reqUnlock} from "@/api/emr-control/connotation-quality-control";
 
 const props = defineProps({
   maxHeight: {
@@ -138,6 +141,9 @@ const wardCreationJudgment = async () => {
   if (canIUnlockIt(unlockEnum.转科创建病历)) {
     return;
   }
+  if (isDev) {
+    return
+  }
   BizException(ExceptionEnum.MESSAGE_ERROR, "当前患者所在病区不在您的管理病区范围内,无法创建病历。");
 }
 
@@ -165,8 +171,6 @@ const handleNodeClick = async (val, node, parent, event) => {
       createId: val.createId,
       createName: val.createName
     }
-
-
     if (val.jump) {
       let tempVal = node.parent.data
       emrMitt.emit('audit', {
@@ -373,6 +377,44 @@ const opt = [
     },
     icon: h(ElIcon, {}, () => h(View))
   },
+  {
+    name: '申请解锁编辑', click: ({unlock}) => {
+      if (!unlock.id) {
+        return xcMessage.error('请选择病历')
+      }
+
+      ElMessageBox.prompt('申请编辑病历', '提示', {
+        type: "warning",
+        closeOnPressEscape: false,
+        closeOnClickModal: false,
+        inputValidator: (val) => {
+          if (XEUtils.isEmpty(val)) {
+            return false;
+          }
+          if (val === null || val.length < 1 || val.length > 50) {
+            return false;
+          }
+        },
+        dangerouslyUseHTMLString: true,
+        inputErrorMessage: '不能为空,最多可输入50个字符。',
+      }).then(({value}) => {
+        let data = {
+          inputRemark: value,
+          code: '',
+          patNo: unlock.patNo,
+          times: unlock.times,
+          flag: 0,
+          emrId: unlock.id
+        }
+        reqUnlock(data)
+      }).catch(() => {
+
+      })
+    },
+    validator: () => {
+      return false
+    },
+  },
   {
     name: '确认排序', click: (data) => {
       let temp = []

+ 78 - 32
src/components/zhu-yuan-yi-sheng/shou-shu-shen-qing/BianJiShouShu.vue

@@ -3,16 +3,14 @@
     <el-row>
       <el-col :span="12">
         <el-form-item label="手术" prop="opCode">
-          <xc-select-v3 v-model="props.data"
-                        @row-click="buildOrderName"
-                        code="opCode"
-                        name="opName"
-                        :data="surgicalItems"
-                        :remote-method="queryOperation">
-            <el-table-column label="编码" prop="code"/>
-            <el-table-column label="名称" prop="name"/>
-            <el-table-column label="类型" prop="opTypeName"/>
-          </xc-select-v3>
+          <xc-combo-grid v-model="props.data"
+                         code="opCode"
+                         @row-click="clickToSelectSurgery"
+                         :table-header="tableHeader"
+                         name="opName"
+                         :query-data-func="queryOperation">
+            <el-table-column prop="opScale" label="手术等级"/>
+          </xc-combo-grid>
         </el-form-item>
       </el-col>
       <el-col :span="12">
@@ -88,10 +86,11 @@
       </el-col>
       <el-col :span="12">
         <el-form-item label="手术等级" prop="opScale">
-          <xc-select-v3 v-model="props.data" code="opScale" name="opScaleName" :data="shouShuDengJi">
-            <xc-table-column label="编码" prop="code"/>
-            <xc-table-column label="名称" prop="name"/>
-          </xc-select-v3>
+          <el-select v-model="props.data.opScale"
+                     :disabled="disabledOpScale">
+            <xc-el-option :data="shouShuDengJi"/>
+          </el-select>
+
         </el-form-item>
       </el-col>
       <el-col :span="12">
@@ -105,8 +104,11 @@
       </el-col>
       <el-col :span="12">
         <el-form-item label="主刀医生" prop="doctorZd">
-          <xc-select-v3 v-model="props.data" code="doctorZd" name="doctorZdName"
-                        :remote-method="suoSouYiShen" clearable
+          <xc-select-v3 v-model="props.data"
+                        code="doctorZd"
+                        name="doctorZdName"
+                        :remote-method="suoSouYiShen"
+                        clearable
                         :data="yiShengShuJu">
             <xc-table-column label="编码" prop="code"/>
             <xc-table-column label="姓名" prop="name"/>
@@ -209,15 +211,18 @@
 </template>
 
 <script setup name="BianJiShouShu">
-import {ref, unref} from "vue";
+import {ref} from "vue";
 import {diagnosisInOurHospital} from "@/api/zhu-yuan-yi-sheng/jian-yan-jian-cha-shen-qing";
-import {huoQuShouShuBuWei, obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
+import {getDoctorByOpCode, huoQuShouShuBuWei, obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
 import {getRenYuan} from "@/api/public-api";
 import moment from "moment";
-import {ElMessage} from "element-plus";
 import XcSelectV3 from "@/components/xiao-chan/select-v3/XcSelectV3.vue";
 import XcTableColumn from "@/components/xiao-chan/select-v3/XcTableColumn";
 import {getFormatDatetime} from "@/utils/date";
+import XcComboGrid from "@/components/xiao-chan/combo-grid/XcComboGrid.vue";
+import XcElOption from "@/components/xiao-chan/xc-el-option/XcElOption.vue";
+import XEUtils from 'xe-utils'
+import {xcMessage} from "@/utils/xiaochan-element-plus";
 
 const props = defineProps({
   anestheticMode: {
@@ -227,15 +232,46 @@ const props = defineProps({
   index: Number
 })
 
+const tableHeader = [
+  {label: '编码', prop: 'code', width: 120},
+  {label: '名称', prop: 'name', width: 240},
+  {label: '类型', prop: 'opTypeName', width: 140},
+]
+
 const emit = defineEmits(['qu-xiao'])
 
-const surgicalItems = ref([])
 const queryOperation = (val) => {
-  obtainSurgicalItems(val, 0).then(res => {
-    surgicalItems.value = res
+  return obtainSurgicalItems(val, 0)
+}
+
+const selectOpRow = ref({})
+const designateASurgeonInChief = ref([])
+const clickToSelectSurgery = (val) => {
+  disabledOpScale.value = false
+  // 01.5922
+  // 39.5000x031
+  selectOpRow.value = val
+
+  getDoctorByOpCode(val.code).then(res => {
+    designateASurgeonInChief.value = res
+    yiShengShuJu.value = res
+    if (designateASurgeonInChief.value.length > 0) {
+      xcMessage.warning('医务部锁定手术主刀医生无法修改');
+    }
   })
+
+  if (val.opScale !== null) {
+    disabledOpScale.value = true
+    props.data.opScale = gradeToCode(val.opScale)
+    xcMessage.warning('医务部锁定手术等级无法修改')
+  }
+
+  buildOrderName()
 }
 
+const disabledOpScale = ref(false)
+
+
 // 手术部位
 const shouShuBuWeiShuJu = ref([])
 // 手术诊断
@@ -261,11 +297,15 @@ const souSuoZhenDuan = (val) => {
 
 // 搜索医生
 const suoSouYiShen = (val) => {
-  if (val.length > 1) {
-    getRenYuan(val).then(res => {
-      yiShengShuJu.value = res
-    })
+
+  if (designateASurgeonInChief.value.length > 0) {
+    return
   }
+
+  getRenYuan(val).then(res => {
+    yiShengShuJu.value = res
+  });
+
 }
 
 
@@ -297,16 +337,22 @@ const jiaoYanGuiZe = ref({
 
 
 const buildOrderName = () => {
-  let temp = `拟于${getFormatDatetime(props.data.opDatetime, 'YYYY-MM-DD HH:mm')}在${props.data.hocusCodeName === null ? '' : props.data.hocusCodeName}下行${props.data.opName}`
-  props.data.orderName = temp
+  props.data.orderName =
+      `拟于${getFormatDatetime(props.data.opDatetime, 'YYYY-MM-DD HH:mm')}在${props.data.hocusCodeName === null ? '' : props.data.hocusCodeName}下行${props.data.opName}`
 }
 
 const shouShuDengJi = [
-  {code: 1, name: '四级'},
-  {code: 2, name: '三级'},
-  {code: 3, name: '二级'},
-  {code: 4, name: '一级'},
+  {code: 1, name: '四级', value: 4},
+  {code: 2, name: '三级', value: 3},
+  {code: 3, name: '二级', value: 2},
+  {code: 4, name: '一级', value: 1},
 ]
+
+const gradeToCode = (val) => {
+  return XEUtils.filter(shouShuDengJi, (item) => {
+    return item.value === val
+  })[0].code
+}
 </script>
 
 <style scoped>

+ 1 - 2
src/components/zhu-yuan-yi-sheng/shou-shu-shen-qing/DaYingShouShuShengQingDan.vue

@@ -56,7 +56,7 @@
 
           <div class="wen-zi">
             <div>麻醉方法:</div>
-            <div>{{ data.hocusCodeName }}</div>
+            <div>{{ data.hocusName }}</div>
           </div>
 
           <div class="tanXingHeZiHeng" style="border-bottom: 1px solid #000">
@@ -151,7 +151,6 @@ import {cptSex} from "@/utils/computed";
 import {getLodop, initLodop} from "@/utils/c-lodop";
 import {ElMessage} from "element-plus";
 
-
 const props = defineProps({
   width: {
     type: Number,

+ 8 - 12
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/EmrControlRuleDialog.vue

@@ -25,18 +25,14 @@ const suggestion = ref({})
 const dialog = ref(false)
 
 onMounted(() => {
-
-  myPatientQualityControl().then((res) => {
-
-    suggestion.value = res
-
-    console.log(res)
-    if (suggestion.value.length > 0) {
-      dialog.value = true
-    }
-
-  });
-
+  if (!isDev) {
+    myPatientQualityControl().then((res) => {
+      suggestion.value = res
+      if (suggestion.value.length > 0) {
+        dialog.value = true
+      }
+    });
+  }
 })
 
 

+ 1 - 12
src/views/hospitalization/zhu-yuan-yi-sheng/Home.vue

@@ -89,21 +89,10 @@ watch(() => yzHeaderHeight.value, () => {
   yzHeaderSize.value = yzHeaderHeight.value
 }, {immediate: true})
 
-const initSocket = () => {
-  const sid = store.getters['user/sid']
-  if (sid && sid !== 'undefined') {
-    initWebSocket(sid + uuid(8, 62))
-  } else {
-    router.push('/login')
-  }
-}
 
 
-onMounted(async () => {
 
-  if (store.getters['user/token']) {
-    initSocket()
-  }
+onMounted(async () => {
   await nextTick()
   getJyJcZdTree().then((res) => {
     jyTree.value = res.jy

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

@@ -404,15 +404,15 @@
 import {getEmrInpatientData} from "@/api/dictionary/emr-data-maintenance-api";
 import EmrSidebar from "@/components/zhu-yuan-yi-sheng/emr/EmrSidebar.vue";
 import {
-  EMRInteractive,
-  emrConfig,
-  fontSizes,
   availableFonts,
+  completeModeSwitch,
   copyEnum,
-  getEmrCopy,
   delEmrCopy,
-  completeModeSwitch,
-  emrMitt
+  emrConfig,
+  EMRInteractive,
+  emrMitt,
+  fontSizes,
+  getEmrCopy
 } from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init";
 import {ElMessage, ElMessageBox} from "element-plus";
 import {BizException, ExceptionEnum} from "@/utils/BizException";
@@ -423,7 +423,8 @@ import {getServerDateApi, getUuid} from "@/api/public-api";
 import {
   audit,
   getDrgIntelligentGrouping,
-  getExtractDataElement, hotSearchSorting,
+  getExtractDataElement,
+  hotSearchSorting,
   submitMedicalRecord
 } from "@/api/zhu-yuan-yi-sheng/emr-patient";
 import {useDocumentVisibility} from "@vueuse/core";
@@ -436,7 +437,7 @@ import EmrPopup from "@/components/zhu-yuan-yi-sheng/emr/EmrPopup.vue";
 import EmrAuxiliaryTools from "@/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/EmrAuxiliaryTools.vue";
 import sleep from "@/utils/sleep";
 import EmrWebSocket from "@/components/zhu-yuan-yi-sheng/emr/web-socket/EmrWebSocket.vue";
-import {listToStr, stringIsBlank, stringNotBlank} from "@/utils/blank-utils";
+import {stringIsBlank} from "@/utils/blank-utils";
 import {isDev, needRule} from "@/utils/public";
 import {isThereADoctorEditing} from "@/api/zhu-yuan-yi-sheng/emr-socket";
 import {onDeactivated} from "vue";
@@ -447,8 +448,11 @@ import EmrAudit
   from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrAudit.vue";
 import EmrConnotation
   from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/EmrConnotation.vue";
-import {save} from "@/api/zhu-yuan-yi-sheng/connotation-quality-control";
+import {createAnEdit, reqUnlock, save} from "@/api/emr-control/connotation-quality-control";
 import XEUtils from 'xe-utils'
+import {
+  EmrEditCreateLimit
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-edit-create-limit";
 
 
 const props = defineProps({
@@ -581,6 +585,7 @@ const emrMainWidth = () => {
 
 const emrEvent = {
   'loaded': async (event) => {
+    // await 判断可否创建或编辑()
     editor = currentEmr.value.getEditor()
     loaded = false
     // 获取医生等级
@@ -603,6 +608,8 @@ const emrEvent = {
     // 每一个病历只能一个人编辑
     await editJudgment()
     openOrClosePage(isOpenPage.value)
+    // 如果创建人不是自己就要开启审阅
+    openTheTraceByUser(createId)
   },
 
   'contentchange': (e, op) => {
@@ -729,7 +736,6 @@ const editJudgment = async () => {
       }).catch(() => {
   })
   // 不能编辑 不锁柱
-  console.log(emrConfig.value.editor)
   if (!emrConfig.value.editor) {
     return
   }
@@ -1083,46 +1089,11 @@ const 入院病历 = 'ruyuanjiluzhuanyong'
  * @returns {*}
  */
 function generalMedicalRecords() {
-  if (whetherToEditFunc(createId)) {
+  if (emrEditCreateLimit.isEdit(createId)) {
     editor.setEditorMode('free')
   } else {
     editor.setEditorMode('readonly')
   }
-  // 如果创建人不是自己就要开启审阅
-  openTheTraceByUser(createId)
-}
-
-/**
- *
- * @param create 创建人
- * @returns {boolean}
- */
-function whetherToEditFunc(create) {
-  let currentCode = userData.code
-  if (emrConfig.value.editor) {
-    if (stringIsBlank(create)) {
-      return true
-    }
-    if (create === currentCode) {
-      return true;
-    }
-    // 科室质控员
-    if (departmentQualityController()) {
-      return true;
-    }
-    switch (doctorLevel) {
-      case 1:
-        return createId === userData.code
-      case 2:
-        // 二级医生不能改三级医生的
-        return createId !== extractFields('主任医生编码');
-      case 3:
-        return true
-    }
-    return false
-  } else {
-    return false
-  }
 }
 
 // 设置编辑器的模式
@@ -1139,7 +1110,7 @@ const setEditorModeFun = () => {
         // 如果没有数据说明的历史数据历史数据就不能用下面的逻辑
         if (XEUtils.isArray(areas)) {
           editor.setEditorMode('free')
-          let edit = whetherToEditFunc(createId)
+          let edit = emrEditCreateLimit.isEdit(createId)
           XEUtils.arrayEach(areas, (item, key) => {
             if (edit) {
               item.target.setReadonly(false)
@@ -1196,67 +1167,50 @@ const courseSegmentLocking = async () => {
     return
   }
   editor.setEditorMode('free')
-  if (documentId) {
-    let courseTitles = [];
-    let loginUserCode = userData.code;
-    循环病程返回数据元((value, node) => {
-      let fragment = node.getAttribute('fragment')
-      let pushData = {
-        code: '查房时间',
-        name: value['查房标题']?.value,
-        value: value['查房时间']?.value,
-        emrDocumentId: documentId,
-        emrCategoryCode: categoryCode,
-        jump: true,
-        createName: createName,
-        createDate: value['查房时间']?.value,
-        createId: createId,
-        type: 'category',
-      }
-      if (fragment != null) {
-        pushData.trueCreationTime = fragment?.creationTime
-      }
-      courseTitles.push(pushData);
-      if (emrConfig.value.editor) {
-        let editorCode = value['编辑者']?.value[0]?.code;
-        if (editorCode) {
-          if (departmentQualityController()) {
-            node.view.setReadonly(false);
-            node.view.setDeletable(false);
-            return
-          }
-          // 只有自己可以删除自己创建的片段
-          if (editorCode === loginUserCode) {
-            node.view.setReadonly(false);
-            node.view.setDeletable(true);
-          } else if (doctorLevel === 1) {
-            if (editorCode !== loginUserCode) {
-              node.view.setReadonly(true);
-              node.view.setDeletable(false);
-            } else {
-              node.view.setReadonly(false);
-              node.view.setDeletable(false);
-            }
-          } else if (doctorLevel === 2) {
-            if (editorCode === extractFields('主任医生编码')) {
-              node.view.setReadonly(true);
-              node.view.setDeletable(false);
-            } else {
-              node.view.setReadonly(false);
-              node.view.setDeletable(false);
-            }
-          } else if (doctorLevel === 3) {
-            node.view.setReadonly(false);
-            node.view.setDeletable(false);
-          }
-        }
-      } else {
+  if (XEUtils.isEmpty(documentId)) {
+    return
+  }
+  let courseTitles = [];
+  循环病程返回数据元((value, node) => {
+    let fragment = node.getAttribute('fragment')
+    let pushData = {
+      code: '查房时间',
+      name: value['查房标题']?.value,
+      value: value['查房时间']?.value,
+      emrDocumentId: documentId,
+      emrCategoryCode: categoryCode,
+      jump: true,
+      createName: createName,
+      createDate: value['查房时间']?.value,
+      createId: createId,
+      type: 'category',
+    }
+    if (fragment != null) {
+      pushData.trueCreationTime = fragment?.creationTime
+    }
+    courseTitles.push(pushData);
+    if (emrConfig.value.editor) {
+      let editorCode = value['编辑者']?.value[0]?.code;
+      if (XEUtils.isEmpty(editorCode)) {
         node.view.setReadonly(true);
         node.view.setDeletable(false);
+      } else {
+        let isEdit = emrEditCreateLimit.isEdit(editorCode)
+        if (isEdit) {
+          node.view.setReadonly(false);
+          //  自己创建的病历片段才可以删除
+          node.view.setDeletable(editorCode === userData.code);
+        } else {
+          node.view.setReadonly(true);
+          node.view.setDeletable(false);
+        }
       }
-    })
-    emrSidebarRef.value.diseaseDurationRecordTime(documentId, courseTitles);
-  }
+    } else {
+      node.view.setReadonly(true);
+      node.view.setDeletable(false);
+    }
+  })
+  emrSidebarRef.value.diseaseDurationRecordTime(documentId, courseTitles);
 }
 
 
@@ -1335,12 +1289,8 @@ const readonlyPattern = () => {
 // 判断 一级 二级 三级医生
 let doctorLevel = $ref(1);
 const doctorLevelFunc = () => {
-  doctorLevel = grade(userData.code)
-  createLevel = grade(createId)
-}
-
-const departmentQualityController = () => {
-  return userData.code === props.huanZheXinXi.zkys
+  doctorLevel = emrEditCreateLimit.getDoctorLevelById(userData.code)
+  createLevel = emrEditCreateLimit.getDoctorLevelById(createId)
 }
 
 function grade(userCode) {
@@ -1637,6 +1587,9 @@ const saveDocumentId = ref()
 
 const visibility = useDocumentVisibility()
 
+// 创建和编辑病历
+let emrEditCreateLimit = new EmrEditCreateLimit(props.huanZheXinXi, userData)
+
 onMounted(async () => {
   pageIsZoom()
   autoSave = store.state.app.emrAutosave;
@@ -1661,6 +1614,7 @@ onMounted(async () => {
     }
   })
 
+
   autoSaveFunc()
 
   emrMitt.on('editor', () => {
@@ -1799,6 +1753,10 @@ const 解析病程记录 = () => {
       saveDialog.close()
       BizException(ExceptionEnum.MESSAGE_ERROR, '片段中存在异常查房时间,请检查。')
     }
+    if (XEUtils.isEmpty(data.createId)) {
+      data.createId = listNull('编辑者')
+    }
+
     node.view.setAttribute('fragment', data)
     fragment.push(data)
   })
@@ -1866,6 +1824,101 @@ function changeRatio() {
   return ratio;
 }
 
+let backToQC = {
+  "id": 1,
+  "name": "",
+  "code": "",
+  "thePromptWasNotCreated": "",
+  "editThePromptStatement": "",
+  "level1DoctorCreatesATimeLimit": 0,
+  "level1DoctorEditingTimeLimit": 0,
+  "theSeniorDoctorCreatesATimeLimit": 0,
+  "sqlId": 0,
+  "sql": "",
+  "sqlName": "",
+  "open": 1,
+  "timeSubtracts": 74
+}
+
+/**
+ * 现在有两个特殊病历
+ * 入院病历需要填写补充诊断
+ * 病程记录锁住
+ */
+const 判断可否创建或编辑 = async () => {
+  backToQC = null
+  规则不可编辑 = false
+  if (typeof props.huanZheXinXi.dischargeDays !== 'undefined'
+      && props.huanZheXinXi.dischargeDays > 7) {
+    return
+  }
+  let data = {
+    code: categoryCode,
+    documentId,
+    patNo: props.huanZheXinXi.inpatientNo,
+    times: props.huanZheXinXi.admissTimes,
+    userCode: userData.code
+  }
+  try {
+    backToQC = await createAnEdit(data)
+    if (stringIsBlank(documentId)) {
+      可否创建()
+    } else {
+      可否编辑()
+    }
+  } catch {
+  }
+}
+
+const 可否创建 = () => {
+  if (backToQC === null) return
+  let 时限 = doctorLevel === 1 ? backToQC.level1DoctorCreatesATimeLimit : backToQC.theSeniorDoctorCreatesATimeLimit;
+  if (backToQC.timeSubtracts > 时限) {
+    let str = `<span style="color: red">${backToQC.thePromptWasNotCreated}</span>
+               医务部通过审核后,方可创建病历。`
+    ElMessageBox.prompt(str, '提示', {
+      type: "warning",
+      closeOnPressEscape: false,
+      closeOnClickModal: false,
+      inputValidator: (val) => {
+        if (XEUtils.isEmpty(val)) {
+          return false;
+        }
+        if (val === null || val.length < 1 || val.length > 50) {
+          return false;
+        }
+      },
+      dangerouslyUseHTMLString: true,
+      inputErrorMessage: '不能为空,最多可输入50个字符。',
+    }).then(({value}) => {
+      let data = {
+        inputRemark: value,
+        code: categoryCode,
+        patNo: props.huanZheXinXi.inpatientNo,
+        times: props.huanZheXinXi.admissTimes,
+        flag: 0
+      }
+      reqUnlock(data)
+    }).catch(() => {
+
+    }).finally(() => {
+      emptyEditor()
+    })
+    throw new Error('')
+  }
+}
+
+let 规则不可编辑 = false
+const 可否编辑 = () => {
+  if (backToQC === null) return
+  if (doctorLevel === 1) {
+    let 时限 = backToQC.level1DoctorEditingTimeLimit
+    if (backToQC.timeSubtracts > 时限) {
+      规则不可编辑 = true
+      ElMessage.error(backToQC.editThePromptStatement)
+    }
+  }
+}
 
 defineExpose({
   closeBothSides,

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

@@ -154,7 +154,6 @@ const routerFunc = async () => {
       emrConfig.value.editor = false
     }
     await nextTick();
-    console.log(patientInfo)
   } else {
     show = false
   }
@@ -169,9 +168,11 @@ const queryActPatient = async () => {
 const queryDisPatient = async () => {
   patientInfo = await getDisPatient(query.value.patNo, query.value.times)
   patientInfo.$inOutFlag = 2
-  dischargeDays = subtractTime(await getServerDateApi(), patientInfo.disDate)
+  patientInfo.dischargeDays = subtractTime(await getServerDateApi(), patientInfo.disDate)
+
   // 如果患者的出院时间大于 7 天就只能看病历和打印病历了
-  emrConfig.value.editor = dischargeDays <= 7;
+  emrConfig.value.editor = patientInfo.dischargeDays <= 7;
+
   // 如果超过了七天就去查看是否有申请编辑
   if (!emrConfig.value.editor) {
     emrConfig.value.editor = canIUnlockIt(unlockEnum.出院编辑)

+ 79 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-edit-create-limit.js

@@ -0,0 +1,79 @@
+import XEUtils from "xe-utils";
+import {needRule} from "@/utils/public";
+
+
+export class EmrEditCreateLimit {
+    constructor(patInfo, userInfo) {
+        this.patInfo = patInfo
+        this.userInfo = userInfo
+        console.log('患者信息', patInfo)
+        console.log('当前登录人员信息', userInfo)
+    }
+
+    /**
+     * 获取医生的等级,根据医生等级
+     * @param userCode 医生编码
+     * @returns {number}
+     */
+    getDoctorLevelById(userCode) {
+        let pat = this.patInfo
+        if (userCode === pat.referPhysician) {
+            return 1;
+        }
+        if (userCode === pat.consultPhysician) {
+            return 2;
+        }
+        if (userCode === pat.deptDirector) {
+            return 3;
+        }
+        if (needRule(1)) {
+            return 3;
+        }
+        return 1;
+    }
+
+    /**
+     * 用来判断是否可以编辑病历
+     * @param created 创建人
+     * @returns {boolean}
+     */
+    isEdit(created) {
+        let userCode = this.userInfo.code
+
+        let 当前医生等级 = this.getDoctorLevelById(userCode)
+        let 创建人等级 = this.getDoctorLevelById(created)
+
+        // 创建人是空的说明是新建的病历
+        if (XEUtils.isEmpty(created)) {
+            return true;
+        }
+        // 如果是患者的病历质控专员可以编辑
+        if (userCode === this.patInfo.zkys) {
+            return true;
+        }
+        // 创建人是自己就可以编辑病历
+        if (created === userCode) {
+            // todo 预留位置,这个地方可以限制创建人是否还可以编辑病历
+            return true;
+        }
+        if (当前医生等级 === 创建人等级) {
+            return false
+        }
+        if (当前医生等级 > 创建人等级) {
+            return true
+        }
+        console.log('当前医生等级 %s , 创建人等级: %s', 当前医生等级, 创建人等级);
+        return false
+    }
+
+    /**
+     * 是否可以创建病历
+     * @param code 病历的 id
+     */
+    isCreate(code) {
+        // todo 判断是否还可以创建病历
+
+    }
+
+
+}

+ 21 - 7
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/ShouShuShenQing.vue

@@ -68,13 +68,13 @@
 
 </template>
 
-<script setup name="ShouShuShenQing">
+<script setup>
 import {computed, ref, watch} from 'vue'
 import {
   huoQuShouShu,
-  huoQuShouShuMingCheng,
-  huoQuShouShuShenQingDaYing,
-  shanChuShouShu, xinZengShouShuShenQing
+  huoQuShouShuShenQingDaYing, preoperativeDiscussion,
+  shanChuShouShu,
+  xinZengShouShuShenQing
 } from '@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing'
 import {
   huanZheXinXi,
@@ -92,6 +92,7 @@ import SurgicalErrorInfo from "@/components/zhu-yuan-yi-sheng/shou-shu-shen-qing
 import {BizException, ExceptionEnum} from "@/utils/BizException";
 import {getWindowSize} from "@/utils/window-size";
 import {isDev} from "@/utils/public";
+import sleep from "@/utils/sleep";
 
 const windowSize = computed(() => {
   return store.state.app.windowSize
@@ -152,16 +153,28 @@ const tabsName = (val, index) => {
 
 const clickSave = async () => {
   if (youWuXuanZheHuanZhe()) return
-  let data = huanZheXinXi.value
+  let res = await preoperativeDiscussion(
+      huanZheXinXi.value.inpatientNo,
+      huanZheXinXi.value.admissTimes,
+      operationApplication.value.length)
+
+
+  let data = huanZheXinXi.value;
   data.execDept = store.state.user.info.deptCode
   data.list = operationApplication.value
 
   try {
-    await ElMessageBox.confirm('是否要生成全排斥医嘱?', '提示', {
+
+    let str = ''
+    if (res) {
+      str += '<span style="color:teal;">仅提示:</span><span style="color: red">按照医务部要求手术申请前需要创建术前讨论病历 <br></span>'
+    }
+    await ElMessageBox.confirm(str + '是否要生成全排斥医嘱?', '提示', {
       type: 'warning',
       confirmButtonText: '生成排斥医嘱',
       cancelButtonText: '生成处置医嘱',
-      distinguishCancelAndClose: true
+      distinguishCancelAndClose: true,
+      dangerouslyUseHTMLString: true
     })
     data.generateRejectedOrders = true
   } catch (e) {
@@ -195,6 +208,7 @@ const removeTab = (targetName) => {
 const dianJiChaKanShouShu = (row) => {
   huoQuShouShuShenQingDaYing(huanZheXinXi.value.inpatientNo, huanZheXinXi.value.admissTimes, row.recordId).then((res) => {
     daYing.value.data = res
+    console.log(res)
     if (stringNotBlank(res.applyDate)) {
       res.applyDate = res.applyDate.split(' ')[0]
     }

+ 9 - 9
src/views/settings/Test.vue

@@ -1,20 +1,20 @@
 <template>
-  <el-button @click="test">测试</el-button>
-  <xc-date-picker v-model="te" :a-few-days-ago="10"/>
+  <xc-combo-grid-v2
+      v-model="model"
+      :query-data-func="queryOperation"/>
 </template>
 
 <script setup>
-import {useZIndex} from "element-plus";
-import XcDatePicker from "@/components/xiao-chan/date-picker/XcDatePicker.vue";
+import XcComboGridV2 from "@/components/xiao-chan/combo-grid/XcComboGridV2.vue";
+import {obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
 
-const te = ref([])
 
-const test = () => {
-  console.log(useZIndex().nextZIndex());
+const model = ref('')
+const data = ref([])
 
+const queryOperation = (val) => {
+  return obtainSurgicalItems(val, 0)
 }
-
-
 </script>
 
 <style lang="scss">