Browse Source

查看手术

xiaochan 1 year ago
parent
commit
aa3a7872a8

+ 2 - 1
package.json

@@ -1,7 +1,8 @@
 {
   "name": "init",
   "version": "0.0.0",
-  "scripts": {    "dev": "vite",
+  "scripts": {
+    "dev": "vite",
     "dev:dev": "vite --mode=dev --port=8998",
     "start": "vite",
     "update:element": "npm install element-plus deps/element-plus.2.0.4.1.tar.gz",

+ 8 - 0
src/api/zhu-yuan-yi-sheng/jian-yan-jian-cha-shen-qing.js

@@ -183,3 +183,11 @@ export function getAncillaryInformation(patNo, times) {
         params: {patNo, times}
     })
 }
+
+export function applicationOpRecord(data) {
+    return requestV2({
+        url: url + "applicationOpRecord",
+        method: 'post',
+        data
+    })
+}

+ 17 - 12
src/api/zhu-yuan-yi-sheng/shou-shu-shen-qing.js → src/api/zhu-yuan-yi-sheng/shou-shu-shen-qing.ts

@@ -1,7 +1,17 @@
+// @ts-ignore
 import request from "@/utils/request";
+import requestV2 from "../../utils/request-v2";
 
 let url = '/shouShuShenQing/'
 
+export function getOpRecord(patNo, times) {
+    return requestV2({
+        url: url + 'getOpRecord',
+        method: 'get',
+        params: {patNo, times}
+    })
+}
+
 export function huoQuShouShu(data) {
     return request({
         url: url + 'huoQuShouShu',
@@ -106,15 +116,10 @@ export function removeSurgicalSite(code) {
     })
 }
 
-
-
-
-
-
-
-
-
-
-
-
-
+export function applicationOpRecord(data) {
+    return requestV2({
+        url: url + 'applicationOpRecord',
+        method: 'post',
+        data
+    })
+}

+ 46 - 26
src/components/xiao-chan/combo-grid/XcComboGridV2.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {computed, defineProps, nextTick, onMounted, ref, watch} from "vue";
+import {computed, nextTick, onMounted, ref, watch} from "vue";
 import XEUtils from 'xe-utils'
 import {useVModels} from "@vueuse/core";
 import {VxeTableInstance} from "vxe-table";
@@ -50,6 +50,14 @@ const props = defineProps({
   keyName: {
     type: Array<string>,
     default: ['code', 'name', 'pyCode', 'wCode', 'dCode']
+  },
+  width: {
+    type: String,
+    default: '172px'
+  },
+  rowClickHide: {
+    type: Boolean,
+    default: true
   }
 });
 
@@ -156,8 +164,9 @@ const change = XEUtils.debounce(async (value) => {
 
 const rowClick = (val) => {
   let {row, rowIndex} = val
-  pullDownRef.value?.hidePanel()
-
+  if (props.rowClickHide) {
+    pullDownRef.value?.hidePanel()
+  }
   if (props.select) {
     modVal.value = row.code
     selectName = row.name
@@ -286,6 +295,14 @@ const handleClear = () => {
   placeholder.value = props.placeholder
 }
 
+const onEsc = () => {
+  pullDownRef.value.hidePanel()
+}
+
+const onTab = () => {
+  pullDownRef.value.hidePanel()
+}
+
 const scroll = (val) => {
   let {
     scrollTop,
@@ -345,7 +362,6 @@ onMounted(async () => {
   if (props.filterable && props.queryDataFunc !== null) {
     console.warn('开启本地搜索后,远程搜索无效。')
   }
-
 })
 
 </script>
@@ -361,10 +377,12 @@ onMounted(async () => {
                 @clear="handleClear"
                 :disabled="props.disabled"
                 ref="inputRef"
-                style="width: 172px;"
+                :style="{width: props.width}"
                 @keydown.up.prevent="keyUp"
                 @keydown.down.prevent="keyDown"
+                @keydown.esc.prevent="onEsc"
                 @keydown.enter.prevent="enterToSelect"
+                @keydown.tab="onTab"
                 @input="change"
                 :clearable="props.clearable"
                 @click="inputClick"
@@ -373,27 +391,29 @@ onMounted(async () => {
                 @focus="handleFocus"/>
     </template>
     <template #dropdown>
-      <vxe-table
-          ref="vxeTableRef"
-          :height="200"
-          @scroll="scroll"
-          border
-          :scroll-x="{gt: 0,enabled: false}"
-          :scroll-y="{gt: 0 ,enabled: true}"
-          :column-config="{resizable: true}"
-          :row-config="{ height: 24,isCurrent: true,isHover:true }"
-          class="vxe-padding_zero vxe-header-max_content hl-style vxe-scroll_15"
-          header-row-class-name="padding_zero "
-          @cell-click="rowClick"
-          show-header-overflow
-          show-overflow
-          :data="computedData">
-        <vxe-column v-for="item in tableHeader"
-                    :title="item.title ? item.title : item.label "
-                    :field="item.field ? item.field : item.prop"
-                    :width="item.width"/>
-        <slot/>
-      </vxe-table>
+      <div style="z-index: 9999999999">
+        <vxe-table
+            ref="vxeTableRef"
+            :height="200"
+            @scroll="scroll"
+            border
+            :scroll-x="{gt: 0,enabled: false}"
+            :scroll-y="{gt: 0 ,enabled: true}"
+            :column-config="{resizable: true}"
+            :row-config="{ height: 24,isCurrent: true,isHover:true }"
+            class="vxe-padding_zero vxe-header-max_content hl-style vxe-scroll_15"
+            header-row-class-name="padding_zero "
+            @cell-click="rowClick"
+            show-header-overflow
+            show-overflow
+            :data="computedData">
+          <vxe-column v-for="item in tableHeader"
+                      :title="item.title ? item.title : item.label "
+                      :field="item.field ? item.field : item.prop"
+                      :width="item.width"/>
+          <slot/>
+        </vxe-table>
+      </div>
     </template>
   </vxe-pulldown>
 </template>

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

@@ -8,8 +8,7 @@
                             @row-click="clickToSelectSurgery"
                             :table-header="tableHeader"
                             name="opName"
-                            :query-data-func="queryOperation">
-          </xc-combo-grid-v2>
+                            :query-data-func="queryOperation"/>
         </el-form-item>
       </el-col>
       <el-col :span="12">
@@ -188,7 +187,7 @@
 <script setup name="BianJiShouShu">
 import {ref} from "vue";
 import {diagnosisInOurHospital} from "@/api/zhu-yuan-yi-sheng/jian-yan-jian-cha-shen-qing";
-import {getDoctorByOpCode,  obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
+import {getDoctorByOpCode, obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
 import moment from "moment";
 import {getFormatDatetime} from "@/utils/date";
 import XcElOption from "@/components/xiao-chan/xc-el-option/XcElOption.vue";

+ 0 - 0
src/data/operation.js → src/data/operation.ts


+ 1 - 1
src/router/modules/dashboard.js

@@ -659,7 +659,7 @@ const route = [
                     },
                     {
                         path: 'shouShuShenQing',
-                        component: createNameComponent(() => import('@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/ShouShuShenQing.vue')),
+                        component: createNameComponent(() => import('@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/ShouShu.vue')),
                         meta: {
                             title: '查看手术',
                         },

+ 23 - 0
src/ts-type/op-record.ts

@@ -0,0 +1,23 @@
+export declare type OpRecord = {
+    opScale: string
+    opDatetime: string
+    orderName: string
+    opCode: string
+    opName: string
+    diagBeforeOp: string
+    diagBeforeCode: string
+    opCodeList?: [],
+    diagList?: [],
+    urgentClinicFlag: string
+    ssbc: number
+    ybSelfFlag: number
+    partCode: string;
+    partCodeName: string
+    hocusCode: string;
+    hocusCodeName: string
+    doctorZd: string
+    doctorZdName: string
+    remark: string
+    applyDate: string
+    generateRejectedOrders?: boolean
+};

+ 41 - 0
src/utils/tutorial-driver.ts

@@ -0,0 +1,41 @@
+import {nextTick} from "vue";
+import sleep from "./sleep";
+
+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, hook: () => void) {
+        this.name = name;
+        this.len = len;
+        this.hook = hook;
+    }
+
+    setId(name: string, splicing: boolean = true) {
+        const id = splicing ? `#${this.name}-${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
+    }
+
+    getId(name: string) {
+        return `#${this.name}-${name}`
+    }
+
+}
+
+export default TutorialDriver

+ 2 - 4
src/views/hospitalization/zhu-yuan-yi-sheng/cao-yao-yi-zhu/XinZhengCaoYao.vue

@@ -168,14 +168,14 @@
   </el-dialog>
 </template>
 
-<script setup name="XinZhenCaoYao">
+<script setup>
 import {computed, onMounted, ref, watch} from 'vue'
 import {
   cuoWuXinXi,
   huanZheXinXi,
   youWuXuanZheHuanZhe,
   mingXi,
-  yzHeaderSize, yzSize
+  yzSize
 } from '@/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng'
 import {
   baoCunCaoYao,
@@ -189,11 +189,9 @@ import {ElMessage, ElMessageBox} from 'element-plus'
 import {stringIsBlank, stringNotBlank} from '@/utils/blank-utils'
 import CaoYaoMuBan from '@/components/zhu-yuan-yi-sheng/cao-yao-yi-zhu/CaoYaoMuBan.vue'
 import {yaoPinXiangMuPiPeiYiBao} from '@/api/public-api'
-import CuoWuXinXi from '@/components/zhu-yuan-yi-sheng/CuoWuXinXi.vue'
 import XcComboGrid from "@/components/xiao-chan/combo-grid/XcComboGrid";
 import sleep from "@/utils/sleep";
 import CaoYaoCuoWuXinXin from "@/components/zhu-yuan-yi-sheng/cao-yao-yi-zhu/CaoYaoCuoWuXinXin.vue";
-import {getWindowSize} from "@/utils/window-size";
 import DeptSelect from "@/components/xiao-chan/dept-code/DeptSelect.vue";
 import Dig from "@/utils/math";
 

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

@@ -1,56 +1,20 @@
-import sleep from "../../../../../utils/sleep";
 import {driver} from "driver.js";
 import {nextTick} from "vue";
 import {initUserInfoConfig, setUserConfigByCode, userInfoConfig} from "../../../../../utils/user-info-config";
 import {query} from './emr-init'
 import {isDev} from "../../../../../utils/public";
+import TutorialDriver from "../../../../../utils/tutorial-driver";
 
-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(() => {
+function elementViewHook() {
     if (isDev) return
     initUserInfoConfig(async (data) => {
         if (data.emrTutorial && query.value.state === 1) {
             await emrTutorialFunc(true)
         }
     })
-})
+}
+
+export const emrTutorial = new TutorialDriver('emr-tutorial', 31, elementViewHook);
 
 export async function emrTutorialFunc(upload: boolean) {
     function name(name: string) {
@@ -307,6 +271,6 @@ export async function emrTutorialFunc(upload: boolean) {
 
 
 export const emrTutorialGetId = (name: string) => {
-    return emrTutorial.getId(name)
+    return emrTutorial.setId(name)
 }
 

+ 16 - 1
src/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng.ts

@@ -712,6 +712,11 @@ export function clickTimeLimitReminder() {
 export const clickOnThePatient = async (patNo: string) => {
     switchPatients();
     huanZheXinXi.value = await getPatientInfo(patNo) as any;
+
+    changePatientFunc.forEach((value, key) => {
+        value()
+    })
+
     getDrgPatInfo(huanZheXinXi.value).then(res => {
         if (res != null) {
             huanZheXinXi.value.groupInfoName = res['name']
@@ -750,7 +755,7 @@ const switchPatients = (): boolean => {
             str += "存在未保存的医嘱。"
         }
     }
-    if (operationApplication.value.length > 0) {
+    if (yzMitt.exists('ssNotSave') && yzMitt.emit('ssNotSave')) {
         str += '存在未保存的手术申请。'
     }
     if (consultationApplication.value) {
@@ -868,6 +873,7 @@ export interface YzMitt {
     rowClick: (data: YzType) => void
     queryFeeByOrderNo: (data: YzType) => void,
     setTimeLimitPrompt: (data: any) => void
+    ssNotSave: () => boolean
 
     [key: string]: (...args: any[]) => any
 }
@@ -927,3 +933,12 @@ export class RefFillingValue<R, F> {
         return this
     }
 }
+
+const changePatientFunc = new Map<string, () => void>();
+export const changePatient = (name: string, cb: () => void) => {
+    changePatientFunc.set(name, cb);
+}
+
+export const unChangePatient = (name: string) => {
+    changePatientFunc.delete(name);
+}

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

@@ -10,14 +10,12 @@
         size="small"
         start-placeholder="开始日期"
         style="width: 280px"
-        type="daterange"
-    ></el-date-picker>
+        type="daterange"/>
     <el-button icon="Search" type="primary" @click="dianJiChaXunShouShu(0)">查询</el-button>
     <el-button icon="Plus" type="success" @click="dianJiXinZhenShouShu">新增</el-button>
     <el-button icon="Check" type="success" @click="clickSave">保存手术</el-button>
     <el-button type="danger" @click="error.dialog = true">打开错误信息</el-button>
   </div>
-
   <div style="display: flex">
     <div>
       <el-table :data="shouShuShuJu.data"
@@ -163,7 +161,6 @@ const clickSave = async () => {
   data.list = operationApplication.value
 
   try {
-
     let str = ''
     if (res) {
       str += '<span style="color:teal;">仅提示:</span><span style="color: red">按照医务部要求手术申请前需要创建术前讨论病历 <br></span>'

+ 99 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/ShouShu.vue

@@ -0,0 +1,99 @@
+<script setup lang="ts">
+import {
+  nextTick,
+  onActivated,
+  onDeactivated,
+  onMounted,
+  ref
+} from "vue";
+import {
+  changePatient,
+  huanZheXinXi,
+  unChangePatient
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng";
+import {getOpRecord, shanChuShouShu} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
+import XcTable from "@/components/xiao-chan/xc-table/XcTable.vue";
+import {windowSizeStore} from "@/utils/store-public";
+import {CyMessageBox} from "@/components/cy/message-box";
+import AddShouShu from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShu.vue";
+import {ssId, ssMitt, ssTutorialId} from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/shou-shu";
+
+const operationData = ref([])
+const tabs = ref('ss-add')
+
+const queryOperation = async () => {
+  if (huanZheXinXi.value.inpatientNo) {
+    getOpRecord(huanZheXinXi.value.inpatientNo, huanZheXinXi.value.admissTimes).then(res => {
+      operationData.value = res
+      console.log(res)
+    })
+  }
+}
+
+const delClick = async (row, index) => {
+  await CyMessageBox.confirm({
+    type: 'delete',
+    message: `您确定要删除【${row.opName}】吗?`
+  })
+  await shanChuShouShu(row.recordId)
+  operationData.value.splice(index, 1)
+}
+
+onDeactivated(() => {
+  unChangePatient('手术')
+})
+
+onActivated(() => {
+  changePatient('手术', queryOperation)
+  queryOperation();
+})
+
+onMounted(async () => {
+  ssMitt.on('query', queryOperation)
+  await nextTick()
+  ssTutorialId('tab-ss-add', false)
+})
+</script>
+
+<template>
+  <el-container>
+    <el-main>
+      <el-tabs v-model="tabs" type="border-card" class="shou_shu">
+        <el-tab-pane label="历史信息" name="ss-lishi">
+          <xc-table :final-height="windowSizeStore.h / 1.1 - 30"
+                    :local-data="operationData">
+            <el-table-column label="申请号" prop="recordId" width="50" show-overflow-tooltip></el-table-column>
+            <el-table-column label="状态" width="30" prop="statusName" show-overflow-tooltip></el-table-column>
+            <el-table-column label="手术名" prop="opName" show-overflow-tooltip></el-table-column>
+            <el-table-column label="手术时间" prop="opDatetime" show-overflow-tooltip></el-table-column>
+            <el-table-column label="术前诊断" prop="diagBeforeOp" show-overflow-tooltip width="90"></el-table-column>
+            <el-table-column label="主刀" prop="doctorZdName" show-overflow-tooltip width="90"></el-table-column>
+            <el-table-column label="1助" prop="doctor1Name" show-overflow-tooltip width="90"></el-table-column>
+            <el-table-column label="麻醉方式" prop="hocusName" show-overflow-tooltip width="90"></el-table-column>
+            <el-table-column label="操作" fixed="right">
+              <template #header>
+                <el-button @click="queryOperation" type="primary">查询</el-button>
+              </template>
+              <template #default="{row,$index}">
+                <el-button icon="Delete" type="danger" @click.stop="delClick(row, $index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </xc-table>
+        </el-tab-pane>
+
+        <el-tab-pane label="新增" name="ss-add">
+          <AddShouShu/>
+        </el-tab-pane>
+
+      </el-tabs>
+    </el-main>
+  </el-container>
+</template>
+
+<style lang="scss">
+.shou_shu {
+  .el-tabs__content {
+    padding: 5;
+  }
+}
+</style>

+ 214 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShu.vue

@@ -0,0 +1,214 @@
+<script setup lang="ts">
+import {onMounted, Ref, ref} from "vue";
+import {getDoctorByOpCode, obtainSurgicalItems} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
+import {diagnosisInOurHospital} from "@/api/zhu-yuan-yi-sheng/jian-yan-jian-cha-shen-qing";
+import AddShouShuTable
+  from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShuTable.vue";
+import XEUtils from "xe-utils";
+import AddShouShuEditor
+  from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShuEditor.vue";
+import type {OpRecord} from "@/ts-type/op-record";
+import {useZIndex} from "element-plus";
+import {getServerDate} from "@/utils/moment-utils";
+import {yzMitt} from "@/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng";
+import {ssMitt, ssTutorialId} from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/shou-shu";
+
+const initAddData = (): OpRecord => {
+  return {
+    diagBeforeCode: "",
+    diagBeforeOp: "",
+    doctorZd: "",
+    doctorZdName: "",
+    hocusCode: "",
+    applyDate: "",
+    partCode: "",
+    partCodeName: "",
+    remark: "",
+    ssbc: 0,
+    urgentClinicFlag: "0",
+    ybSelfFlag: 0,
+    diagList: [],
+    hocusCodeName: "",
+    opCode: "",
+    opCodeList: [],
+    opDatetime: '',
+    opName: "",
+    opScale: "",
+    orderName: ""
+  }
+}
+
+const addData: Ref<OpRecord> = ref(initAddData())
+const opCodeFuncData = ref([])
+const diagListFuncData = ref([])
+const temp = ref({})
+const opGrade = new Map<string, any>();
+const disabledOpScale = ref(false)
+const designateASurgeonInChief = ref([])
+const editorRef = ref<{
+  buildOrderName: () => void
+}>()
+
+const tableHeader = [
+  {label: '编码', prop: 'code', width: 120},
+  {label: '名称', prop: 'name', width: 200},
+  {label: '类型', prop: 'opTypeName', width: 100},
+  {label: '等级', prop: 'opScale', width: 40},
+]
+
+const queryOperation = (val) => {
+  return obtainSurgicalItems(val, 0)
+}
+
+const checkOneOpCodeList: () => void = XEUtils.debounce(async function () {
+  designateASurgeonInChief.value = []
+  disabledOpScale.value = false
+
+  if (addData.value.opCodeList.length > 0) {
+    const item = addData.value.opCodeList[0] as {
+      opScale: string
+      code: string
+      name: string
+    }
+
+    addData.value.opCode = item.code
+    addData.value.opName = item.name
+
+    if (!opGrade.has(item.code)) {
+      const res = await getDoctorByOpCode(item.code)
+      if (res.length > 0) {
+        opGrade.set(item.code, res)
+      }
+    }
+    if (opGrade.has(item.code)) {
+      designateASurgeonInChief.value = opGrade.get(item.code)
+      addData.value.doctorZd = ''
+      addData.value.doctorZdName = ''
+    }
+
+
+    if (item.opScale !== null) {
+      disabledOpScale.value = true
+      addData.value.opScale = item.opScale
+    }
+
+    editorRef.value.buildOrderName()
+  }
+}, 500)
+
+const changeDiagList = () => {
+  if (addData.value.diagList.length === 0) return
+  const item = addData.value.diagList[0] as {
+    code: string
+    name: string
+  }
+  addData.value.diagBeforeCode = item.code
+  addData.value.diagBeforeOp = item.name
+}
+
+
+const clearData = () => {
+  addData.value = initAddData()
+  opCodeFuncData.value = []
+  diagListFuncData.value = []
+  opGrade.clear()
+}
+
+const zIndex = ref(0)
+const markShow = ref(true)
+
+const nextIndex = () => {
+  zIndex.value = useZIndex().nextZIndex()
+}
+
+const addClick = async () => {
+  clearData()
+  const now = await getServerDate()
+  addData.value.applyDate = now;
+  addData.value.opDatetime = now;
+  markShow.value = false
+}
+
+const createMark = () => {
+  clearData()
+  markShow.value = true
+  nextIndex()
+}
+
+onMounted(() => {
+  nextIndex()
+  yzMitt.on('ssNotSave', () => {
+    return !markShow.value
+  })
+  ssMitt.on('markVisible', () => {
+    return markShow.value
+  })
+})
+</script>
+
+<template>
+  <div :id="ssTutorialId('add-click')">
+    <div style="display: flex; position: relative">
+      <div class="shou_shu-masking" v-show="markShow" @click="addClick">
+        <div class="shou_shu-add">
+          添加
+        </div>
+      </div>
+      <div>
+        <AddShouShuTable
+            title="手术"
+            @changeList="checkOneOpCodeList"
+            v-model:data="addData.opCodeList"
+            :query-function="queryOperation"
+            v-model:query-data="opCodeFuncData"
+        />
+      </div>
+      <div style="margin-left: 5px">
+        <AddShouShuTable
+            title="诊断"
+            @changeList="changeDiagList"
+            v-model:data="addData.diagList"
+            :query-function="diagnosisInOurHospital"
+            v-model:query-data="diagListFuncData"
+        />
+      </div>
+      <div class="shou_shu-editor">
+        <AddShouShuEditor
+            ref="editorRef"
+            v-model:data="addData"
+            :disabled-op-scale="disabledOpScale"
+            :designate-a-surgeon-in-chief="designateASurgeonInChief"
+            @clear="createMark"/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.shou_shu-editor {
+  flex: 1;
+}
+
+.shou_shu-masking {
+  position: absolute;
+  height: 100%;
+  width: 100%;
+  z-index: v-bind(zIndex);
+  background: var(--el-overlay-color-light);
+  display: flex;
+  cursor: pointer;
+
+  .shou_shu-add {
+    margin: auto;
+    width: 20%;
+    height: 20%;
+    display: flex;
+    border-radius: 5px;
+    justify-content: center;
+    align-items: center;
+    color: white;
+
+    border: 1px dashed white;
+  }
+}
+</style>

+ 343 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShuEditor.vue

@@ -0,0 +1,343 @@
+<script setup lang="ts">
+import {onMounted, Ref, ref} from 'vue'
+import {useVModel} from "@vueuse/core";
+import type {OpRecord} from "@/ts-type/op-record";
+import {getFormatDatetime} from "@/utils/date";
+import XcElOption from "@/components/xiao-chan/xc-el-option/XcElOption.vue";
+import {shouShuDengJi, surgicalSituation} from "@/data/operation";
+import XcComboGridV2 from "@/components/xiao-chan/combo-grid/XcComboGridV2.vue";
+import {
+  applicationOpRecord,
+  huoQuShouShuBuWei,
+  preoperativeDiscussion
+} from "@/api/zhu-yuan-yi-sheng/shou-shu-shen-qing";
+import {getRenYuan, maZuiFangShi} from "@/api/public-api";
+import {useCompRef} from "@/utils/useCompRef";
+import {ElForm} from "element-plus";
+import moment from 'moment'
+import {CyMessageBox} from "@/components/cy/message-box";
+import {
+  huanZheXinXi
+} from "@/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng";
+import {userInfoStore} from "@/utils/store-public";
+import XEUtils from "xe-utils";
+import {formatDateToStr} from "@/utils/moment-utils";
+import {ssMitt} from "@/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/shou-shu";
+
+const props = defineProps<{
+  data: OpRecord,
+  designateASurgeonInChief: any[];
+  disabledOpScale: boolean
+}>()
+const emits = defineEmits(['update:data', 'clear'])
+const dataModel = useVModel(props, 'data', emits) as Ref<OpRecord>
+// 麻醉方式
+const anestheticMode = ref([])
+// 人员
+const personnelList = ref([])
+// 手术部位
+const surgicalSiteList = ref([])
+
+const personnel = [
+  {label: '编码', prop: 'code', width: 46},
+  {label: '名称', prop: 'name', width: 56},
+  {label: '科室', prop: 'deptName', width: 92},
+  {label: '级别', prop: 'empTitName', width: 105},
+]
+
+const buildOrderName = () => {
+  dataModel.value.orderName =
+      `拟于${getFormatDatetime(dataModel.value.opDatetime, 'YYYY-MM-DD HH:mm')}在${dataModel.value.hocusCodeName === null ? '' : dataModel.value.hocusCodeName}下行${dataModel.value.opName}`
+}
+
+// 时间校验
+const shenQingShiJian = (rule, value, callback) => {
+  const oDate1 = formatDateToStr(value)
+  const oDate2 = formatDateToStr(dataModel.value.applyDate)
+  if (moment(oDate1).isBefore(moment(oDate2))) {
+    callback(new Error('手术时间不能小于申请时间'))
+  } else {
+    callback()
+  }
+}
+
+// 校验数据
+const rules = ref({
+  opCode: [{required: true, message: '项目编码不能为空', trigger: 'blur'}],
+  opName: [{required: true, message: '项目名称不能为空', trigger: 'blur'}],
+  applyDate: [{required: true, message: '申请时间不能为空', trigger: 'blur'}],
+  opDatetime: [
+    {required: true, message: '手术时间不能为空', trigger: 'blur'},
+    {validator: shenQingShiJian, trigger: 'blur'}],
+  diagBeforeCode: [{required: true, message: '术前诊断不能为空', trigger: 'blur'}],
+  partCode: [{required: true, message: '手术部位不能为空', trigger: 'blur'}],
+  doctorZd: [{required: true, message: '主刀医生不能为空', trigger: 'blur'}],
+  opScale: [{required: true, message: '手术等级不能为空', trigger: 'blur'}],
+  orderName: [{required: true, message: '手术等级不能为空', trigger: 'blur'}],
+})
+
+const formRef = useCompRef(ElForm)
+const loading = ref(false)
+const updateClick = async () => {
+  await formRef.value.validate()
+  const res = await preoperativeDiscussion(huanZheXinXi.value.inpatientNo, huanZheXinXi.value.admissTimes, 1) as boolean
+
+  const data = XEUtils.clone(huanZheXinXi.value, true) as any;
+  data.execDept = userInfoStore.value.deptCode
+  data.opRecord = dataModel.value
+
+  data.opCodeList = dataModel.value.opCodeList
+  data.diagList = dataModel.value.diagList
+
+  const str = res ? '<span style="color:teal;">仅提示:</span><span style="color: red">按照医务部要求手术申请前需要创建术前讨论病历 <br></span>' : ''
+  let next = true
+
+  await CyMessageBox.confirm({
+    type: "info",
+    message: str + '是否生成处置医嘱',
+    confirmButtonText: '处置医嘱',
+    cancelButtonText: '排斥医嘱',
+    dangerouslyUseHTMLString: true,
+  }).then(() => {
+    data.generateRejectedOrders = true
+  }).catch(({action}) => {
+    if (action === 'cancel') {
+      data.generateRejectedOrders = false
+    } else {
+      next = false
+    }
+  })
+
+  if (next) {
+    loading.value = true
+    await applicationOpRecord(data).then(() => {
+      delClick()
+      ssMitt.emit('query')
+    }).finally(() => {
+      loading.value = false
+    })
+  }
+
+}
+
+const delClick = () => {
+  emits('clear')
+}
+
+
+onMounted(() => {
+  maZuiFangShi().then(res => {
+    anestheticMode.value = res as any
+  })
+  getRenYuan('').then(res => {
+    personnelList.value = res as any
+  })
+  huoQuShouShuBuWei('').then(res => {
+    surgicalSiteList.value = res
+  })
+})
+
+defineExpose({
+  buildOrderName
+})
+</script>
+
+<template>
+  <el-form ref="formRef" :model="dataModel" :rules="rules" label-width="80px">
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="手术时间" prop="opDatetime">
+          <el-date-picker v-model="dataModel.opDatetime"
+                          @change="buildOrderName"
+                          format="YYYY-MM-DD HH:mm:ss"
+                          placeholder="time"
+                          type="datetime"
+                          value-format="YYYY-MM-DD HH:mm:ss"/>
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="情况">
+          <el-select v-model="dataModel.urgentClinicFlag">
+            <XcElOption :data="surgicalSituation"/>
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="班次">
+          <el-switch
+              v-model="dataModel.ssbc"
+              :active-value="1"
+              :inactive-value="2"
+              active-color="#13ce66"
+              active-text="正常"
+              inactive-color="#ff4949"
+              inactive-text="加班">
+          </el-switch>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="自费">
+          <el-switch
+              v-model="dataModel.ybSelfFlag"
+              :active-value="1"
+              :inactive-value="0"
+              active-color="#13ce66"
+              active-text="是"
+              inactive-color="#ff4949"
+              inactive-text="否">
+          </el-switch>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="手术部位" prop="partCode">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code="partCode"
+                            name="partCodeName"
+                            :data="surgicalSiteList"
+                            filterable/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="手术等级" prop="opScale">
+          <div>
+            <div v-if="disabledOpScale" class="shou_shu-warning">医务部锁定等级</div>
+            <el-select v-model="dataModel.opScale"
+                       :disabled="disabledOpScale">
+              <xc-el-option :data="shouShuDengJi"/>
+            </el-select>
+          </div>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="麻醉方式">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            @row-click="buildOrderName"
+                            filterable
+                            :data="anestheticMode"
+                            code-name="hocusCode"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="主刀医生" prop="doctorZd">
+          <div>
+            <div v-if="designateASurgeonInChief.length > 0" class="shou_shu-warning">
+              医务部锁定主刀医生
+            </div>
+            <xc-combo-grid-v2 v-model="dataModel"
+                              code-name="doctorZd"
+                              filterable
+                              :table-header="personnel"
+                              :data="designateASurgeonInChief.length > 0 ? designateASurgeonInChief : personnelList"/>
+          </div>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="第一助手">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code="doctor1"
+                            name="doctor1Name"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="第二助手">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code-name="doctor2"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="第三助手">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code-name="doctor3"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="麻醉医生">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code-name="doctorMz"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="器械护士">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code-name="nurseQx"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item label="巡回护士">
+          <xc-combo-grid-v2 v-model="dataModel"
+                            code-name="nurseXh"
+                            filterable
+                            clearable
+                            :table-header="personnel"
+                            :data="personnelList"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="24">
+        <el-form-item label="医嘱名:" prop="orderName">
+          <el-input style="width: 220px"
+                    :rows="3"
+                    type="textarea"
+                    show-word-limit
+                    maxlength="50"
+                    v-model="dataModel.orderName"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="24">
+        <el-form-item label="附注说明">
+          <el-input v-model="dataModel.remark" :rows="6" maxlength="50" show-word-limit
+                    style="width: 450px;"
+                    type="textarea"/>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="24">
+        <el-form-item>
+          <el-button :loading="loading" type="primary" @click="updateClick">保存</el-button>
+          <el-button :loading="loading" type="danger" @click="delClick">删除</el-button>
+        </el-form-item>
+      </el-col>
+
+    </el-row>
+  </el-form>
+</template>
+
+<style lang="scss">
+.shou_shu-warning {
+  color: #eebe77;
+  font-size: 12px;
+}
+</style>

+ 115 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/components/AddShouShuTable.vue

@@ -0,0 +1,115 @@
+<script setup lang="ts">
+import {useVModel} from "@vueuse/core";
+import XEUtils from "xe-utils";
+import {nextTick, onMounted, Ref, ref} from 'vue'
+import XcComboGridV2 from "@/components/xiao-chan/combo-grid/XcComboGridV2.vue";
+import {windowSizeStore} from "@/utils/store-public";
+import {useCompRef} from "@/utils/useCompRef";
+import {ElTable} from "element-plus";
+// @ts-ignore
+import Sortable from 'sortablejs';
+
+const props = defineProps({
+  data: Array,
+  title: String,
+  queryFunction: Function,
+  queryData: Array
+})
+const emits = defineEmits(['update:data', 'update:queryData', 'changeList'])
+const modelData = useVModel(props, 'data', emits) as Ref<any[]>
+const queryDayaModel = useVModel(props, 'queryData', emits) as Ref<any[]>
+const temp = ref({})
+
+const findIndex = (arr: any[], name: string, value: string) => {
+  return XEUtils.findIndexOf(arr, item => {
+    return item[name] === value;
+  })
+}
+
+const delClock = (index) => {
+  modelData.value.splice(index, 1)
+  emits('changeList')
+}
+
+const rowClick = (row) => {
+  const index = findIndex(modelData.value, 'code', row.code)
+  if (index > -1) {
+    delClock(index);
+  } else {
+    modelData.value.push({
+      code: row.code,
+      name: row.name,
+      opScale: row.opScale
+    })
+  }
+  emits('changeList')
+}
+
+const tableRef = useCompRef(ElTable)
+
+const initSortable = () => {
+  const el = tableRef.value.$el.querySelector('tbody')
+  const opt = {
+    animation: 200,
+    handle: '.el-table__row',
+    onEnd({newIndex, oldIndex}) {
+      const currRow = modelData.value.splice(oldIndex, 1)[0]
+      modelData.value.splice(newIndex, 0, currRow)
+      emits('changeList')
+    }
+  }
+  Sortable.create(el, opt)
+}
+
+onMounted(async () => {
+  await nextTick()
+  initSortable();
+})
+</script>
+
+<template>
+  {{ props.title }}
+  <br/>
+  <XcComboGridV2 v-model="temp"
+                 code-name="aa"
+                 :row-click-hide="false"
+                 @rowClick="rowClick"
+                 v-model:data="queryDayaModel"
+                 :query-data-func="props.queryFunction"/>
+  <el-table :data="props.data"
+            row-key="code"
+            ref="tableRef"
+            :max-height="windowSizeStore.h  / 1.2 - 20">
+    <el-table-column :label="`${props.title}名称`" prop="name">
+      <template #default="{row,$index}">
+        <span class="ss_text" :class="$index ===0? 'main' : 'secondary'">
+          {{ $index === 0 ? '主' : '次' }}
+        </span>
+        {{ row.name }}
+      </template>
+    </el-table-column>
+    <el-table-column>
+      <template #default="{$index}">
+        <el-button type="danger" @click="delClock($index)">删除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<style>
+
+.ss_text {
+  padding: 3px;
+  border-radius: 5px;
+  margin-right: 5px;
+
+  &.main {
+    background: red;
+  }
+
+  &.secondary {
+    background: #8f8787;
+  }
+}
+
+</style>

+ 79 - 0
src/views/hospitalization/zhu-yuan-yi-sheng/shou-shu-shen-qing/src/shou-shu.ts

@@ -0,0 +1,79 @@
+import EventBus from "../../../../../utils/mitt";
+import TutorialDriver from "../../../../../utils/tutorial-driver";
+import {driver} from "driver.js";
+import {initUserInfoConfig, setUserConfigByCode, userInfoConfig} from "../../../../../utils/user-info-config";
+import {nextTick} from "vue";
+import sleep from "../../../../../utils/sleep";
+import {xcMessage} from "../../../../../utils/xiaochan-element-plus";
+
+declare type SsMitt = {
+    query: () => void;
+    markVisible: () => boolean
+}
+export const ssMitt = new EventBus<SsMitt>();
+
+function elementViewHook() {
+    initUserInfoConfig(async (data) => {
+        await sleep(150)
+        await ssTutorialFunc()
+    })
+}
+
+export const ssTutorial = new TutorialDriver('ss-tutorial', 2, elementViewHook);
+
+export async function ssTutorialFunc() {
+    const getName = (name: string) => {
+        return ssTutorial.getId(name);
+    }
+
+    const steps = (element: string, title: string, description: string) => {
+        return {
+            element: getName(element),
+            popover: {title, description,}
+        };
+    }
+
+    const opt = {
+        showProgress: true,
+        allowClose: false,
+        nextBtnText: '下一步',
+        prevBtnText: '上一步',
+        doneBtnText: '关闭',
+        onDestroyStarted: () => {
+            driverObj.destroy();
+        },
+        onNextClick: (el: HTMLElement) => {
+            const id = el.id;
+            if (id === 'pane-ss-add' && ssMitt.emit('markVisible')) {
+                return
+            }
+            driverObj.moveNext()
+        },
+        steps: [
+            {
+                element: '#tab-ss-add',
+                popover: {
+                    title: '新增手术',
+                    description: '点击“新增”,进入新增标签页。',
+                }
+            },
+            {
+                element: '#pane-ss-add',
+                popover: {
+                    title: '点击',
+                    description: '点击黑色区域任意位置,进行添加手术,请先点击添加,才能进行下一步操作。'
+                }
+            }
+        ]
+    }
+
+    const driverObj = driver(opt)
+
+    await nextTick();
+    driverObj.drive();
+}
+
+export const ssTutorialId = (name: string, splicing: boolean = true) => {
+    return ssTutorial.setId(name, splicing)
+}
+