فهرست منبع

初步完成门诊电子病历

xiaochan 4 ماه پیش
والد
کامیت
9981e53dd5

+ 0 - 41
src/api/mz-emr/mz-emr.js

@@ -1,41 +0,0 @@
-import request from '../../utils/request'
-
-export function getMzEmrModel() {
-    return request({
-        url: '/mzEmr/getMzEmrModel',
-        method: 'get',
-    })
-}
-
-export function queryMzEmrTree(data) {
-    return request({
-        url: '/mzEmr/queryMzEmrTree',
-        method: 'post',
-        data,
-    })
-}
-
-export function queryMzPatientInfo(data) {
-    return request({
-        url: '/mzEmr/queryMzPatientInfo',
-        method: 'post',
-        data,
-    })
-}
-
-export function saveMzEmrModel(data) {
-    return request({
-        url: '/mzEmr/saveMzEmrModel',
-        method: 'post',
-        data,
-    })
-}
-
-
-
-
-
-
-
-
-

+ 101 - 0
src/api/mz-emr/mz-emr.ts

@@ -0,0 +1,101 @@
+import requestV2 from "@/utils/request-v2";
+
+export function getMzEmrModel() {
+  return requestV2({
+    url: "/mzEmr/getMzEmrModel",
+    method: "get",
+  });
+}
+
+export function queryMzEmrTree(data) {
+  return requestV2({
+    url: "/mzEmr/queryMzEmrTree",
+    method: "post",
+    data,
+  });
+}
+
+export function queryMzPatientInfo(data) {
+  return requestV2({
+    url: "/mzEmr/queryMzPatientInfo",
+    method: "post",
+    data,
+  });
+}
+
+export function saveMzEmrModel(data) {
+  return requestV2({
+    url: "/mzEmr/saveMzEmrModel",
+    method: "post",
+    data,
+  });
+}
+
+export function getEmrModelByPatientId(patientNo, times) {
+  return requestV2({
+    url: "/mzEmr/getEmrModelByPatientId",
+    method: "get",
+    params: {
+      patientNo,
+      times,
+    },
+  });
+}
+
+export function addDir(data) {
+  return requestV2({
+    url: "/mzEmr/addDir",
+    method: "post",
+    data,
+  });
+}
+
+export function addFile(data) {
+  return requestV2({
+    url: "/mzEmr/addFile",
+    method: "post",
+    data,
+  });
+}
+
+export function updateFile(data) {
+  return requestV2({
+    url: "/mzEmr/updateFile",
+    method: "post",
+    data,
+  });
+}
+
+export function delFile(documentId) {
+  return requestV2({
+    url: "/mzEmr/delFile",
+    method: "get",
+    params: {
+      documentId,
+    },
+  });
+}
+
+export function getRecycleBinMedicalRecords(patientNo, times) {
+  return requestV2({
+    url: "/mzEmr/getRecycleBinMedicalRecords",
+    method: "get",
+    params: { patientNo, times },
+  });
+}
+
+export function restoreMedicalRecords(documentId) {
+  return requestV2({
+    url: "/mzEmr/restoreMedicalRecords",
+    method: "get",
+    params: { documentId },
+  });
+}
+
+export function getHistoryTimes(patientNo, times) {
+  return requestV2({
+    url: "/mzEmr/getHistoryTimes",
+    method: "get",
+    params: { patientNo, times },
+  });
+}

+ 167 - 118
src/router/modules/dashboard.ts

@@ -1,127 +1,176 @@
 // @ts-nocheck
-import {createNameComponent} from '../createNode'
-import {RouteRecordRaw} from "vue-router";
+import { createNameComponent } from "../createNode";
+import { RouteRecordRaw } from "vue-router";
 
-declare module 'vue-router' {
-    interface RouteMeta {
-        title?: string;
-        hideTabs?: boolean,
-        passRule?: boolean,
-        showMenu?: boolean,
-        mainCard?: boolean,
-        mainRolling?: boolean;
-    }
+declare module "vue-router" {
+  interface RouteMeta {
+    title?: string;
+    hideTabs?: boolean;
+    passRule?: boolean;
+    showMenu?: boolean;
+    mainCard?: boolean;
+    mainRolling?: boolean;
+  }
 }
 
 const route: Array<RouteRecordRaw> = [
-    {
-        path: '/login',
-        name: 'login',
-        component: createNameComponent(() => import('@/views/system/login.vue'), 'login'),
-        hideMenu: true,
-        meta: {title: '登录', hideTabs: true},
-    }, {
-        path: '/jumpRedirect',
-        name: 'jumpRedirect',
-        component: createNameComponent(() => import('@/views/system/JumpRedirect.vue'),
-            'jumpRedirect'),
-        hideMenu: true,
-        meta: {title: '跳转重定向', hideTabs: true},
+  {
+    path: "/login",
+    name: "login",
+    component: createNameComponent(
+      () => import("@/views/system/login.vue"),
+      "login"
+    ),
+    hideMenu: true,
+    meta: { title: "登录", hideTabs: true },
+  },
+  {
+    path: "/jumpRedirect",
+    name: "jumpRedirect",
+    component: createNameComponent(
+      () => import("@/views/system/JumpRedirect.vue"),
+      "jumpRedirect"
+    ),
+    hideMenu: true,
+    meta: { title: "跳转重定向", hideTabs: true },
+  },
+  {
+    path: "/mzEmr/:patientInfo?",
+    name: "mzEmr",
+    component: createNameComponent(
+      () => import("@/views/mz-emr/MzEmr.vue"),
+      "mzEmr"
+    ),
+    hideMenu: true,
+    meta: { title: "门诊电子病历", hideTabs: true },
+  },
+  {
+    path: "/shareholderCard/:patientInfo?",
+    name: "shareholderCard",
+    component: createNameComponent(
+      () => import("@/views/mz-emr/ShareholderCard.vue"),
+      "shareholderCard"
+    ),
+    hideMenu: true,
+    meta: { title: "股东卡优惠", hideTabs: true },
+  },
+  {
+    path: "/scrollSource",
+    name: "scrollSource",
+    component: createNameComponent(
+      () => import("@/views/single-page/TodayClinicResource.vue"),
+      "shareholderCard"
+    ),
+    hideMenu: true,
+    meta: { title: "今日号源", hideTabs: true },
+  },
+  {
+    path: "/lottery",
+    name: "lottery",
+    component: createNameComponent(
+      () => import("@/views/single-page/Lottery.vue"),
+      "lottery"
+    ),
+    meta: { title: "抽奖", hideTabs: true },
+  },
+  {
+    path: "/inpatientBoard",
+    name: "inpatientBoard",
+    component: createNameComponent(
+      () => import("@/views/single-page/InpatientBoard.vue"),
+      "inpatientBoard"
+    ),
+    meta: { title: "护理看板", hideTabs: true },
+  },
+  {
+    path: "/inpatientBoardV2",
+    name: "inpatientBoardV2",
+    component: createNameComponent(
+      () => import("@/views/single-page/InpatientBoardV2/Index.vue"),
+      "inpatientBoardV2"
+    ),
+    meta: { title: "护理看板", hideTabs: true },
+  },
+  {
+    path: "/myEmrEditor/:pat?/:refresh?",
+    name: "myEmrEditor",
+    component: createNameComponent(
+      () =>
+        import(
+          "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/Home.vue"
+        ),
+      "myEmrEditor",
+      true
+    ),
+    meta: { hideMenu: true, title: "电子病历", hideTabs: true },
+  },
+  {
+    path: "/mzEmrEditorV2",
+    name: "mzEmrEditorV2",
+    component: createNameComponent(
+      () => import("@/views/mz-emr/emr-v2/index.vue"),
+      "mzEmrEditorV2",
+      true
+    ),
+    meta: { hideMenu: true, title: "门诊电子病历", hideTabs: true },
+  },
+  {
+    path: "/fluorescenceTest",
+    name: "fluorescenceTest",
+    component: createNameComponent(
+      () =>
+        import(
+          "@/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/fluorescence-test/FluorescenceTest.vue"
+        ),
+      "fluorescenceTest"
+    ),
+    hideMenu: true,
+    meta: { title: "荧光检验", hideTabs: true },
+  },
+  {
+    path: "/siSettleDetailList/:patientId?/:times?",
+    name: "siSettleDetailList",
+    component: createNameComponent(
+      () =>
+        import("@/views/medical-insurance/allpatient/SiSettleDetailList.vue"),
+      "siSettleDetailList"
+    ),
+    meta: { title: "医保结算单", hideTabs: true },
+  },
+  {
+    path: "/view/patient360",
+    name: "patient360",
+    component: () => import("@/views/view/patient360/src/Patient360.vue"),
+    meta: { title: "患者360", hideTabs: true },
+  },
+  {
+    path: "/inspectionReportV2/:patNo?/:start?/:end?",
+    name: "inspectionReportV2",
+    component: () => import("@/views/examination/InspectionReportIndex.vue"),
+    meta: { title: "检验报告" },
+  },
+  {
+    path: "/blank",
+    name: "blank",
+    component: () => import("@/views/system/Blank.vue"),
+    meta: { hideTabs: true },
+  },
+  {
+    path: "/:path(.*)*",
+    name: "notFound",
+    component: () => import("@/views/system/404.vue"),
+    meta: {
+      hideTabs: true,
     },
-    {
-        path: '/mzEmr/:patientInfo?',
-        name: 'mzEmr',
-        component: createNameComponent(() => import('@/views/mz-emr/MzEmr.vue'), 'mzEmr'),
-        hideMenu: true,
-        meta: {title: '门诊电子病历', hideTabs: true},
+  },
+  {
+    path: "/500",
+    name: "pageError",
+    component: import("@/views/system/500.vue"),
+    meta: {
+      hideTabs: true,
     },
-    {
-        path: '/shareholderCard/:patientInfo?',
-        name: 'shareholderCard',
-        component: createNameComponent(() => import('@/views/mz-emr/ShareholderCard.vue'), 'shareholderCard'),
-        hideMenu: true,
-        meta: {title: '股东卡优惠', hideTabs: true},
-    },
-    {
-        path: '/scrollSource',
-        name: 'scrollSource',
-        component: createNameComponent(() => import('@/views/single-page/TodayClinicResource.vue'), 'shareholderCard'),
-        hideMenu: true,
-        meta: {title: '今日号源', hideTabs: true},
-    },
-    {
-        path: '/lottery',
-        name: 'lottery',
-        component: createNameComponent(() => import('@/views/single-page/Lottery.vue'), 'lottery'),
-        meta: {title: '抽奖', hideTabs: true}
-    },
-    {
-        path: '/inpatientBoard',
-        name: 'inpatientBoard',
-        component: createNameComponent(() => import('@/views/single-page/InpatientBoard.vue'), 'inpatientBoard'),
-        meta: {title: '护理看板', hideTabs: true}
-    },
-    {
-        path: '/inpatientBoardV2',
-        name: 'inpatientBoardV2',
-        component: createNameComponent(() => import('@/views/single-page/InpatientBoardV2/Index.vue'), 'inpatientBoardV2'),
-        meta: {title: '护理看板', hideTabs: true}
-    },
-    {
-        path: '/myEmrEditor/:pat?/:refresh?',
-        name: 'myEmrEditor',
-        component: createNameComponent(
-            () => import('@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/Home.vue'),
-            'myEmrEditor',
-            true),
-        meta: {hideMenu: true, title: '电子病历', hideTabs: true},
-    },
-    {
-        path: '/fluorescenceTest',
-        name: 'fluorescenceTest',
-        component: createNameComponent(() => import('@/components/zhu-yuan-yi-sheng/emr/auxiliary-tools/fluorescence-test/FluorescenceTest.vue'), 'fluorescenceTest'),
-        hideMenu: true,
-        meta: {title: '荧光检验', hideTabs: true},
-    },
-    {
-        path: '/siSettleDetailList/:patientId?/:times?',
-        name: 'siSettleDetailList',
-        component: createNameComponent(() => import('@/views/medical-insurance/allpatient/SiSettleDetailList.vue'), 'siSettleDetailList'),
-        meta: {title: '医保结算单', hideTabs: true},
-    },
-    {
-        path: '/view/patient360',
-        name: 'patient360',
-        component: () => import('@/views/view/patient360/src/Patient360.vue'),
-        meta: {title: '患者360', hideTabs: true},
-    },
-    {
-        path: '/inspectionReportV2/:patNo?/:start?/:end?',
-        name: 'inspectionReportV2',
-        component: () => import('@/views/examination/InspectionReportIndex.vue'),
-        meta: {title: '检验报告'}
-    },
-    {
-        path: '/blank',
-        name: 'blank',
-        component: () => import('@/views/system/Blank.vue'),
-        meta: {hideTabs: true},
-    },
-    {
-        path: '/:path(.*)*',
-        name: 'notFound',
-        component: () => import('@/views/system/404.vue'),
-        meta: {
-            hideTabs: true
-        }
-    }, {
-        path: '/500',
-        name: 'pageError',
-        component: import('@/views/system/500.vue'),
-        meta: {
-            hideTabs: true
-        }
-    }
+  },
 ];
 
-export default route
+export default route;

+ 17 - 1
src/utils/emr/emr-init-v2.ts

@@ -5,6 +5,7 @@ import { BizException, ExceptionEnum } from "../BizException";
 import { ElMessageBox } from "element-plus";
 import XEUtils from "xe-utils";
 import { getEmrToken } from "@/api/zhu-yuan-yi-sheng/emr-patient";
+import { useUserStore } from "@/pinia/user-store";
 
 type AppContext =
   | any
@@ -73,7 +74,7 @@ export interface UseEmrInitReturn {
 
 export interface LoadParams {
   //文档id
-  documentId: string | null;
+  documentId?: string | null;
   // 文档类型
   categoryCode?: string;
   // 患者id
@@ -234,6 +235,21 @@ export function useEmrInit(
   });
 }
 
+export const getCurrentPersonnelInformation = () => {
+  const userInfo = useUserStore().userInfo;
+  const patientData = {};
+  patientData["编辑者"] = [{ code: userInfo.code, name: userInfo.name }];
+  patientData["编辑者编码"] = userInfo.code;
+  patientData["编辑者姓名"] = userInfo.name;
+  patientData["编辑者科室"] = [
+    { code: userInfo.deptCode, name: userInfo.deptName },
+  ];
+  patientData["编辑者科室编码"] = userInfo.deptCode;
+  patientData["编辑者科室名称"] = userInfo.deptName;
+  patientData.user_token = localStorage.token;
+  return patientData;
+};
+
 export function getBcjlUserInfo(value: any): {
   code: string;
   name: string;

+ 12 - 10
src/views/mz-emr/MzEmr.vue

@@ -16,11 +16,7 @@ import { useUserStore } from "@/pinia/user-store";
 import env from "@/utils/setting";
 
 const userInfo = useUserStore().userInfo;
-let patNo: {
-  times: number;
-  patientId: string;
-  userIdCode: string;
-} = {
+let patNo: { times: number; patientId: string; userIdCode: string } = {
   times: 2,
   patientId: "",
   userIdCode: "",
@@ -187,11 +183,11 @@ const appContext = () => {
   };
 };
 
-const handlePrint = (val:any) => {
+const handlePrint = (val: any) => {
   editor.editor.execute("print", {
     value: {
       showPreview: true,
-      mode: val == 'server' ? 'backend' : ''
+      mode: val == "server" ? "backend" : "",
     },
   });
 };
@@ -330,9 +326,15 @@ const modelFlag = ref<boolean>(false);
           <div style="width: 100%; text-align: right">
             <el-button type="success" @click="saveData">保存</el-button>
             <el-button icon="Printer" type="success" @click="handlePrint('')">
-              页面打印</el-button>
-            <el-button icon="Printer" type="success" @click="handlePrint('server')">
-              服务打印</el-button>
+              页面打印
+            </el-button>
+            <el-button
+              icon="Printer"
+              type="success"
+              @click="handlePrint('server')"
+            >
+              服务打印
+            </el-button>
           </div>
           <div>
             <el-button-group class="ml-4">

+ 17 - 0
src/views/mz-emr/emr-v2/comp/MzEditorHistory.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { useEmrInit } from "@/utils/emr/emr-init-v2";
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+
+const { mutation } = inject(useMzEmrStoreKey) as UseMzEmrStoreType;
+const divRef = shallowRef();
+
+onMounted(() => {
+  useEmrInit(divRef.value).then(res => {
+    mutation.setHistoryEditor(res.editor, res.runtime, res);
+  });
+});
+</script>
+
+<template>
+  <div ref="divRef" class="layout_h-w_max"></div>
+</template>

+ 48 - 0
src/views/mz-emr/emr-v2/comp/MzEditorMain.vue

@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+import { useEmrInit } from "@/utils/emr/emr-init-v2";
+import env from "@/utils/setting";
+
+const { userInfo, mutation, emrEventFun } = inject(
+  useMzEmrStoreKey
+) as UseMzEmrStoreType;
+
+const divRef = shallowRef();
+
+const tempData = {
+  editorConfig: {
+    editorMode: "free",
+  },
+  endpoints: {
+    app: env.VITE_DATA_BASE + "/thyy/api/dataEmr/comp",
+    his: env.VITE_BASE_URL,
+  },
+  input: {
+    user: userInfo.code,
+    name: userInfo.name,
+  },
+  userCode: userInfo.code,
+  login: {
+    token: localStorage.token,
+    user: {
+      id: userInfo.code,
+      name: userInfo.name,
+    },
+  },
+};
+
+onMounted(() => {
+  useEmrInit(divRef.value, {
+    appContext: tempData,
+    event: emrEventFun,
+  }).then(res => {
+    mutation.setEditor(res.editor, res.runtime, res);
+  });
+});
+</script>
+
+<template>
+  <div class="layout_h-w_max" ref="divRef"></div>
+</template>
+
+<style lang="scss"></style>

+ 130 - 0
src/views/mz-emr/emr-v2/comp/MzEmrFun.vue

@@ -0,0 +1,130 @@
+<script setup lang="ts">
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+
+const { store, userInfo, emrMutation, mutation } = inject(
+  useMzEmrStoreKey
+) as UseMzEmrStoreType;
+
+const funs = [
+  {
+    name: "保存",
+    fun: () => {
+      emrMutation.save();
+    },
+  },
+  {
+    name: "删除",
+    fun: () => {
+      emrMutation.del();
+    },
+  },
+  {
+    name: "回收站",
+    fun: () => {
+      mutation.openMedicalRecords();
+    },
+  },
+  {
+    name: "打印",
+    items: [
+      {
+        name: "页面打印",
+        fun: () => {
+          emrMutation.print("html");
+        },
+      },
+      {
+        name: "服务打印",
+        fun: () => {
+          emrMutation.print("server");
+        },
+      },
+    ],
+  },
+];
+</script>
+
+<template>
+  <div class="mz-emr_v2--header_func">
+    <div class="mz-emr_v2--header_func_items">
+      <div
+        v-for="item in funs"
+        :key="item.name"
+        @click="item.fun"
+        class="mz-emr_v2--header_func--item"
+      >
+        <div v-if="item.items">
+          <el-popover
+            popper-class="mz-emr_v2--header_func_popper"
+            :width="0"
+            placement="bottom-start"
+            :offset="0"
+            :show-arrow="false"
+            transition=""
+          >
+            <template #reference>
+              <div @click="item.items[0].fun()">
+                {{ item.name }}
+              </div>
+            </template>
+            <div
+              v-for="i in item.items"
+              :key="i.name"
+              @click="i.fun"
+              class="mz-emr_v2--header_func--item-item"
+            >
+              {{ i.name }}
+            </div>
+          </el-popover>
+        </div>
+        <div v-else>
+          {{ item.name }}
+        </div>
+      </div>
+    </div>
+    <div>
+      <span>工号:{{ userInfo.codeRs }}&nbsp;&nbsp;&nbsp;</span>
+      <span>名称:{{ userInfo.name }}</span>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.mz-emr_v2--header_func_popper {
+  padding: 5px !important;
+  min-width: 0 !important;
+
+  .mz-emr_v2--header_func--item-item {
+    padding: 0 5px;
+
+    &:hover {
+      background: #4169e1;
+      color: white;
+      cursor: pointer;
+    }
+  }
+}
+
+.mz-emr_v2--header_func {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 20px;
+  line-height: 30px;
+  background-color: white;
+
+  .mz-emr_v2--header_func_items {
+    display: flex;
+  }
+
+  .mz-emr_v2--header_func--item {
+    padding: 0 5px;
+
+    &:hover {
+      background: #4169e1;
+      color: white;
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 47 - 0
src/views/mz-emr/emr-v2/comp/MzEmrMedicalRecords.vue

@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { useEmrInit, UseEmrInitReturn } from "@/utils/emr/emr-init-v2";
+import { UseDialogType } from "@/components/cy/CyDialog/index";
+import { ElMessageBox } from "element-plus";
+import { restoreMedicalRecords } from "@/api/mz-emr/mz-emr";
+
+const props = defineProps<{
+  data: any[];
+}>();
+
+const divRef = shallowRef();
+
+let edit: UseEmrInitReturn;
+
+function handleClick(value) {
+  edit.loadAndSetDocument({
+    documentId: value.emrDocumentId,
+  });
+}
+
+onMounted(() => {
+  useEmrInit(divRef.value).then(res => {
+    edit = res;
+  });
+});
+
+defineExpose<UseDialogType.Expose>({
+  async confirm() {
+    await ElMessageBox.confirm("是否恢复改病历?", "提示", {
+      type: "success",
+    });
+    return restoreMedicalRecords(edit.editor.documentData._id);
+  },
+});
+</script>
+
+<template>
+  <div class="layout_container layout-horizontal">
+    <aside>
+      <el-table :data="props.data" @row-click="handleClick">
+        <el-table-column prop="name" label="名称" width="250" />
+        <el-table-column prop="createDate" label="创建时间" width="80" />
+      </el-table>
+    </aside>
+    <div class="layout_main" ref="divRef"></div>
+  </div>
+</template>

+ 22 - 0
src/views/mz-emr/emr-v2/comp/MzHeader.vue

@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+
+const { store, userInfo } = inject(useMzEmrStoreKey) as UseMzEmrStoreType;
+</script>
+
+<template>
+  <div class="mz-emr_v2--header_pat-info">
+    <div>
+      门诊号:{{ store.patientInfo.patientId }} 就诊次数:{{
+        store.patientInfo.times
+      }}
+      名字:{{ store.patientInfo.name }}
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.mz-emr_v2--header_pat-info {
+  background: white;
+}
+</style>

+ 162 - 0
src/views/mz-emr/emr-v2/comp/aside/AddEmrDialog.vue

@@ -0,0 +1,162 @@
+<script setup lang="ts">
+import CyTreeInput from "@/components/cy/tree-input/src/CyTreeInput.vue";
+import { useCompShallowRef } from "@/utils/useCompRef";
+import { ElTree } from "element-plus";
+import { Document, Files } from "@element-plus/icons-vue";
+import { useEmrInit, UseEmrInitReturn } from "@/utils/emr/emr-init-v2";
+import { EditType } from "@/utils/emr/edit";
+import { BizException, ExceptionEnum } from "@/utils/BizException";
+import { addFile } from "@/api/mz-emr/mz-emr";
+import { useUserStore } from "@/pinia/user-store";
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+import { isDev } from "@/utils/public";
+
+const props = defineProps<{
+  data: any[];
+}>();
+
+const emits = defineEmits(["addFile"]);
+
+const { store } = inject(useMzEmrStoreKey) as UseMzEmrStoreType;
+
+const treeRef = useCompShallowRef(ElTree);
+const divRef = shallowRef();
+const userInfo = useUserStore().userInfo;
+
+const tmpStore = reactive({
+  loading: true,
+  dialog: false,
+  parent: "",
+
+  id: null,
+  name: "",
+});
+
+let edit: EditType, editFun: UseEmrInitReturn;
+
+function nodeClick(data, node) {
+  if (data.type === "group-category") return;
+  editFun
+    .loadAndSetDocument({
+      categoryId: data._id,
+      categoryCode: data.code,
+    })
+    .then(res => {
+      edit?.setValues(
+        {
+          ...store.emrFillData.extractData,
+          ...store.emrFillData.patientData,
+        },
+        true,
+        true
+      );
+      tmpStore.name = data.name;
+      tmpStore.id = data._id;
+    });
+}
+
+const eventFun = {
+  componentClick: (evt, view) => {
+    const eleInfo = view.getAttribute("element");
+    isDev && eleInfo && console.log(eleInfo);
+  },
+};
+
+function opened() {
+  if (!tmpStore.loading) return;
+  useEmrInit(divRef.value, { event: eventFun }).then(res => {
+    edit = res.editor;
+    editFun = res;
+    tmpStore.loading = false;
+  });
+}
+
+function cancel() {
+  tmpStore.dialog = false;
+  edit!.execute("resetBlankEditor");
+  tmpStore.id = null;
+  tmpStore.name = "";
+  tmpStore.parent = "";
+}
+
+async function confirm() {
+  if (tmpStore.id === null) {
+    BizException(ExceptionEnum.MESSAGE_ERROR, "请选择一个模板");
+  }
+  const document = edit!.getDocument();
+  document.properties.patientId =
+    store.patientInfo.patientId + "_" + store.patientInfo.times;
+  document.properties.modifier = userInfo.name;
+  document.properties.modifierId = userInfo.code;
+
+  const tmpFile = await addFile({
+    patientId: store.patientInfo.patientId,
+    times: store.patientInfo.times,
+    document: {
+      document,
+    },
+    emrCategoryCode: document.properties.categoryCode,
+    name: tmpStore.name,
+    parent: tmpStore.parent,
+  });
+  emits("addFile", tmpFile);
+  cancel();
+}
+
+defineExpose({
+  openDialog(parent: string) {
+    tmpStore.dialog = true;
+    tmpStore.parent = parent;
+  },
+});
+</script>
+
+<template>
+  <el-dialog
+    class="cy_dialog-v2"
+    v-model="tmpStore.dialog"
+    title="新增病历"
+    fullscreen
+    :show-close="false"
+    :close-on-press-escape="false"
+    @opened="opened"
+  >
+    <div
+      class="layout-horizontal layout_container"
+      v-loading="tmpStore.loading"
+    >
+      <aside>
+        <cy-tree-input :tree-ref="treeRef">
+          <template #default="{ handelFilter }">
+            <el-tree
+              ref="treeRef"
+              :data="props.data"
+              :filter-node-method="handelFilter"
+              :props="{
+                children: 'children',
+                label: 'name',
+              }"
+              default-expand-all
+              @nodeClick="nodeClick"
+            >
+              <template #default="{ data }">
+                <el-icon>
+                  <Files v-if="data.type === 'group-category'" />
+                  <Document v-else />
+                </el-icon>
+                &nbsp;&nbsp;{{ data.name }}
+              </template>
+            </el-tree>
+          </template>
+        </cy-tree-input>
+      </aside>
+      <div class="layout_main" ref="divRef"></div>
+    </div>
+    <template #footer>
+      <el-button type="danger" size="default" @click="cancel">取消</el-button>
+      <el-button type="primary" size="default" @click="confirm">确认</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss"></style>

+ 29 - 0
src/views/mz-emr/emr-v2/comp/aside/MzEmrTemplate.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import TabsResizer from "@/components/cy/cy-el-tabs/TabsResizer.vue";
+import TabPaneResizer from "@/components/cy/cy-el-tabs/TabPaneResizer.vue";
+import {
+  useMzEmrStoreKey,
+  UseMzEmrStoreType,
+  VueFile,
+} from "@/views/mz-emr/emr-v2";
+
+const { store } = inject(useMzEmrStoreKey) as UseMzEmrStoreType;
+</script>
+
+<template>
+  <TabsResizer
+    min="100"
+    max="500"
+    value="200"
+    v-model="store.templateTableValue"
+  >
+    <TabPaneResizer label="当前" :name="0">
+      <VueFile.MzPatientTree />
+    </TabPaneResizer>
+    <TabPaneResizer label="历史" :name="1">
+      <VueFile.MzHistoryTree />
+    </TabPaneResizer>
+  </TabsResizer>
+</template>
+
+<style lang="scss"></style>

+ 58 - 0
src/views/mz-emr/emr-v2/comp/aside/MzHistoryTree.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { getHistoryTimes } from "@/api/mz-emr/mz-emr";
+import { useMzEmrStoreKey, UseMzEmrStoreType } from "@/views/mz-emr/emr-v2";
+import IframeEditor from "@/views/settings/dashboard-editor/iframe-editor.vue";
+
+const { store, emrMutation } = inject(useMzEmrStoreKey) as UseMzEmrStoreType;
+
+const data = ref([]);
+
+function handleData(res: any[]) {
+  const map = new Map<number, any[]>();
+
+  res.forEach(item => {
+    if (map.has(item.times)) {
+      map.get(item.times).push(item);
+    } else {
+      map.set(item.times, [item]);
+    }
+  });
+
+  map.forEach(function (value, key) {
+    data.value.push({
+      name: `第${key}次`,
+      children: value,
+    });
+  });
+}
+
+onMounted(() => {
+  getHistoryTimes(store.patientInfo.patientId, store.patientInfo.times).then(
+    (res: any[]) => {
+      if (res.length === 0) {
+        data.value = [];
+      } else {
+        data.value = [];
+        handleData(res);
+      }
+    }
+  );
+});
+</script>
+
+<template>
+  <div class="layout_h-w_max">
+    <el-tree
+      :data="data"
+      :props="{ label: 'name' }"
+      @nodeClick="
+        val => {
+          if (!val.emrDocumentId) return;
+          emrMutation.readOnly(val);
+        }
+      "
+    ></el-tree>
+  </div>
+</template>
+
+<style lang="scss"></style>

+ 141 - 0
src/views/mz-emr/emr-v2/comp/aside/MzPatientTree.vue

@@ -0,0 +1,141 @@
+<script setup lang="ts">
+import { ElMessageBox, ElTree } from "element-plus";
+import { Document, Files } from "@element-plus/icons-vue";
+import CyTreeInput from "@/components/cy/tree-input/src/CyTreeInput.vue";
+import { useCompShallowRef } from "@/utils/useCompRef";
+import {
+  addDir,
+  getEmrModelByPatientId,
+  getMzEmrModel,
+} from "@/api/mz-emr/mz-emr";
+import {
+  useMzEmrStoreKey,
+  UseMzEmrStoreType,
+  type MzEmrPatientData,
+  VueFile,
+} from "@/views/mz-emr/emr-v2";
+import ContextMenu from "@imengyu/vue3-context-menu";
+import XEUtils from "xe-utils";
+
+const { store, emrMutation, mutation } = inject(
+  useMzEmrStoreKey
+) as UseMzEmrStoreType;
+const treeRef = useCompShallowRef(ElTree);
+const addFileDialog = shallowRef();
+
+const queryPatientEmrTree = mutation.queryPatientEmr;
+
+function handleAddDir(parent: string) {
+  ElMessageBox.prompt("请输入文件夹名称", "新增文件夹", {
+    type: "success",
+  })
+    .then(({ value }) => {
+      addDir({
+        parent,
+        name: value,
+        patientId: store.patientInfo.patientId,
+        times: store.patientInfo.times,
+      }).then(() => {
+        queryPatientEmrTree();
+      });
+    })
+    .catch(XEUtils.noop);
+}
+
+function handleAddEmr(parent: string) {
+  addFileDialog.value.openDialog(parent);
+}
+
+function handleCbAddEmr(res) {
+  queryPatientEmrTree();
+  emrMutation.setEmrData(res);
+}
+
+function handleContextmenu(e, data: MzEmrPatientData) {
+  ContextMenu.showContextMenu({
+    x: e.x,
+    y: e.y,
+    items: [
+      {
+        label: "新增文件夹",
+        disabled: data.emrCategoryCode !== "This is Dir",
+        onClick: () => {
+          handleAddDir(data.emrDocumentId);
+        },
+      },
+      {
+        label: "新建病历",
+        disabled: data.emrCategoryCode !== "This is Dir",
+        onClick: () => {
+          handleAddEmr(data.emrDocumentId);
+        },
+      },
+    ],
+  });
+}
+
+onMounted(() => {
+  getMzEmrModel().then(res => {
+    store.templateTree = XEUtils.toArrayTree(res, {
+      key: "_id",
+      parentKey: "parent",
+    });
+  });
+});
+</script>
+
+<template>
+  <div class="layout_h-w_max">
+    <VueFile.AddEmrDialog
+      ref="addFileDialog"
+      :data="store.templateTree"
+      @addFile="handleCbAddEmr"
+    />
+    <cy-tree-input :tree-ref="treeRef" :remote-method="queryPatientEmrTree">
+      <template #default="{ handelFilter }">
+        <el-tree
+          ref="treeRef"
+          class="mz-emr-patient_tree"
+          :filter-node-method="handelFilter"
+          default-expand-all
+          :props="{
+            label: 'emrName',
+            children: 'children',
+          }"
+          :data="store.emrData"
+          @nodeClick="val => emrMutation.setEmrData(val)"
+          @node-contextmenu="handleContextmenu"
+          highlight-current
+        >
+          <template #default="{ data }">
+            <div class="mz-emr-patient_tree_label">
+              <el-icon>
+                <Files v-if="data.emrCategoryCode === 'This is Dir'" />
+                <Document v-else />
+              </el-icon>
+              <div>
+                <div>{{ data.name }}</div>
+                <div v-if="data.emrCategoryCode !== 'This is Dir'">
+                  {{ data.createName }}
+                  {{ data.createDate }}
+                </div>
+              </div>
+            </div>
+          </template>
+        </el-tree>
+      </template>
+    </cy-tree-input>
+  </div>
+</template>
+
+<style lang="scss">
+.mz-emr-patient_tree {
+  --el-tree-node-content-height: "max-content";
+
+  .mz-emr-patient_tree_label {
+    padding: 4px 0;
+    display: flex;
+    align-content: center;
+  }
+}
+</style>

+ 463 - 0
src/views/mz-emr/emr-v2/index.tsx

@@ -0,0 +1,463 @@
+import { useUserStore } from "@/pinia/user-store";
+import { xcMessage } from "@/utils/xiaochan-element-plus";
+import router from "@/router";
+import XEUtils from "xe-utils";
+import {
+  delFile,
+  getEmrModelByPatientId,
+  getRecycleBinMedicalRecords,
+  queryMzPatientInfo,
+  updateFile,
+} from "@/api/mz-emr/mz-emr";
+import { EditType, Runtime } from "@/utils/emr/edit";
+import {
+  getCurrentPersonnelInformation,
+  UseEmrInitReturn,
+} from "@/utils/emr/emr-init-v2";
+import MzEditorMain from "@/views/mz-emr/emr-v2/comp/MzEditorMain.vue";
+import { BizException, ExceptionEnum } from "@/utils/BizException";
+import { magicApi } from "@/utils/database/magic-api-request";
+import { ElMessageBox, ElNotification } from "element-plus";
+import { UseWebSocketReturn } from "@vueuse/core";
+import * as socketFun from "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/socket/useEmrSocket";
+import { useCySocket } from "@/utils/useCySocket";
+import { useEmrCaSignUtils } from "@/views/mz-emr/emr-v2/useEmrCaSign";
+import { isDev } from "@/utils/public";
+import { useDialog } from "@/components/cy/CyDialog/index";
+
+interface PatientInfo {
+  patientId: string;
+  times: number;
+  userIdCode: string;
+  userName: string;
+  deptCode: string;
+  deptName: string;
+  name: string;
+  sex: string;
+  age: number;
+  birthDay: string;
+  socialNo: string;
+  type: null;
+}
+
+export enum TemplateTableValue {
+  当前,
+  历史,
+}
+
+export enum MzEmrTag {
+  当前 = "当前",
+  历史 = "只读",
+  同时打开 = "同时打开",
+}
+
+export interface MzEmrPatientData {
+  id?: number;
+  patNo?: string;
+  times?: number;
+  emrDocumentId?: string;
+  emrCategoryCode?: string;
+  delFlag?: number;
+  emrName?: string;
+  name?: string;
+  createId?: string;
+  createDate?: Date | string;
+  modifyId?: string;
+  modifyDate?: Date | string;
+  submit?: number;
+  parent?: string;
+  referPhysician?: string;
+  consultPhysician?: string;
+  deptDirector?: string;
+  submitId?: string;
+  reviewDoctors?: string;
+  reviewTime?: Date | string;
+  submitTime?: Date | string;
+  sort?: number;
+  children?: MzEmrPatientData[];
+  documentData?: any; // or a more specific type if you know the structure
+  userIdCode?: string;
+}
+
+function queryEmrPatientInfo(store) {
+  function query() {
+    return magicApi({
+      url: "/mzEmrData/getMzEmrInfo",
+      method: "get",
+      params: {
+        patientId: store.patientInfo.patientId,
+        times: store.patientInfo.times,
+      },
+    })
+      .then(res => {
+        store.emrFillData = res;
+        store.emrFillData.currentEditorUser = getCurrentPersonnelInformation();
+      })
+      .catch(() => {
+        notice();
+      });
+  }
+
+  function notice() {
+    const tmp = ElNotification.error({
+      title: "获取患者信息失败",
+      duration: 0,
+      message: (
+        <div style={{ textAlign: "right" }}>
+          <el-button
+            type="primary"
+            onClick={() => {
+              tmp.close();
+              query();
+            }}
+          >
+            重试
+          </el-button>
+        </div>
+      ),
+    });
+  }
+
+  return {
+    query,
+  };
+}
+
+export const VueFile = {
+  // 患者的基本信息这一块
+  MzHeader: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/MzHeader.vue")
+  ),
+  // 主要的电子病历编辑这一块
+  MzEditorMain: MzEditorMain,
+  // 左侧边栏的病历模板这一块
+  MzEmrTemplate: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/aside/MzEmrTemplate.vue")
+  ),
+  // 左侧边栏的当前患者病历树这一块
+  MzPatientTree: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/aside/MzPatientTree.vue")
+  ),
+  // 添加患者电子病历的弹窗这一块
+  AddEmrDialog: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/aside/AddEmrDialog.vue")
+  ),
+  // 电子病历的快捷功能这一块
+  MzEmrFun: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/MzEmrFun.vue")
+  ),
+  // 一个只读的编辑器
+  MzEditorHistory: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/MzEditorHistory.vue")
+  ),
+  MzEmrMedicalRecords: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/MzEmrMedicalRecords.vue")
+  ),
+  MzHistoryTree: defineAsyncComponent(
+    () => import("@/views/mz-emr/emr-v2/comp/aside/MzHistoryTree.vue")
+  ),
+};
+
+const useEditorSocket = () => {
+  // @ts-ignore
+  let socket: UseWebSocketReturn<any> = { close: () => 0 };
+
+  const open = (id: string) => {
+    socket.close();
+    const sid = `${socketFun.URL}/mzEmrEditor/${id}`;
+
+    socket = useCySocket(sid, {
+      socketMsg: {},
+      setInfo: true,
+      onDisconnected(ws, event) {
+        if (event.code == 3001) {
+          const value = JSON.parse(event.reason);
+          ElMessageBox.alert(
+            `编辑者:【${value.name}】,科室:【${value.deptName}】正在编辑病历,请医生退出后,重新打开病历。`,
+            "提示",
+            {
+              type: "error",
+            }
+          );
+        }
+      },
+    });
+  };
+
+  return {
+    open,
+    getSocket: () => socket,
+  };
+};
+
+export const useMzEmrStore = () => {
+  const store = reactive({
+    urlParams: {} as {
+      times: number;
+      patientId: string;
+      userIdCode: string;
+    },
+
+    isSave: false,
+
+    // 是否准备好了
+    prepare: false,
+
+    // 患者信息
+    patientInfo: {} as PatientInfo,
+
+    // 左侧边栏的值
+    templateTableValue: TemplateTableValue.当前 as TemplateTableValue,
+    // 电子病历的模板树状图
+    templateTree: [],
+
+    emrFillData: {} as {
+      extractData: any;
+      patientData: any;
+      currentEditorUser: any;
+      copy: boolean;
+    },
+
+    // 当前编辑的病历参数
+    currentEditParams: {
+      emrDocumentId: null,
+    },
+
+    // 患者的病历
+    emrData: [],
+
+    // 电子病历的tag值
+    emrTag: MzEmrTag.当前,
+  });
+
+  const userInfo = useUserStore().userInfo;
+
+  let editor: EditType, runtime: Runtime, mainEdit: UseEmrInitReturn;
+  let historyEditor: EditType,
+    historyRuntime: Runtime,
+    historyMainEdit: UseEmrInitReturn;
+
+  const editSocket = useEditorSocket();
+
+  const caUtils = useEmrCaSignUtils();
+
+  const mutation = {
+    setEditor(e: EditType, r: Runtime, value: UseEmrInitReturn) {
+      editor = e;
+      runtime = r;
+      mainEdit = value;
+
+      caUtils.install(e);
+
+      editor!.setShortcutKey("CTRL+S", () => {
+        emrMutation.save();
+      });
+    },
+    setHistoryEditor(e: EditType, r: Runtime, value: UseEmrInitReturn) {
+      historyEditor = e;
+      historyRuntime = r;
+      historyMainEdit = value;
+    },
+    async queryPatientEmr() {
+      const data = await getEmrModelByPatientId(
+        store.patientInfo.patientId,
+        store.patientInfo.times
+      );
+      store.emrData = data;
+    },
+    getFillData() {
+      const { copy, ...val } = store.emrFillData;
+      return XEUtils.assign(
+        {},
+        val.extractData,
+        val.patientData,
+        val.currentEditorUser
+      );
+    },
+    async openMedicalRecords() {
+      const data = await getRecycleBinMedicalRecords(
+        store.patientInfo.patientId,
+        store.patientInfo.times
+      );
+
+      await useDialog(VueFile.MzEmrMedicalRecords, {
+        dialogProps: { fullscreen: true, title: "恢复病历" },
+        params: { data },
+      });
+      mutation.queryPatientEmr();
+    },
+  };
+  const tmpEmrFun = {
+    setEmrData(value, forcedEditing = false) {
+      if (value.emrCategoryCode === "This is Dir") {
+        return;
+      }
+      if (!forcedEditing) {
+        if (value.createId !== userInfo.code) {
+          // xcMessage.warning(
+          //   "当前病历的创建人不是您,如果想要编辑请右键编辑打开"
+          // );
+          xcMessage.warning("当前病历的创建人不是您,无法编辑病历");
+          this.readOnly(value);
+          return;
+        }
+      }
+
+      mainEdit
+        .loadAndSetDocument({
+          documentId: value.emrDocumentId,
+        })
+        .then(() => {
+          editor.setValues(mutation.getFillData(), false, true);
+          editSocket.open(value.emrDocumentId);
+          store.currentEditParams = value;
+          if (store.emrTag === MzEmrTag.历史) {
+            store.emrTag = MzEmrTag.同时打开;
+          }
+        });
+    },
+    readOnly(value) {
+      historyEditor.setEditorMode("readonly");
+      historyMainEdit
+        .loadAndSetDocument({
+          documentId: value.emrDocumentId,
+        })
+        .then(() => {
+          if (this.getId() == null) {
+            store.emrTag = MzEmrTag.历史;
+          } else {
+            store.emrTag = MzEmrTag.同时打开;
+          }
+        });
+    },
+    print(name) {
+      mainEdit.print(name);
+    },
+    getId() {
+      if (editor) {
+        return editor.documentData._id;
+      }
+      return null;
+    },
+    async save() {
+      if (store.isSave) return;
+      try {
+        if (store.currentEditParams.emrDocumentId === null) {
+          BizException(ExceptionEnum.MESSAGE_ERROR, "请先选择病历");
+        }
+        if (editSocket.getSocket().status.value !== "OPEN") {
+          BizException(ExceptionEnum.MESSAGE_ERROR, "未连接服务器无法保存");
+        }
+        store.isSave = true;
+
+        try {
+          const validator = editor.getValidator();
+          const valid = validator.valid(true);
+          if (valid) {
+            xcMessage.error("校验未通过");
+            return;
+          }
+        } catch {}
+
+        await caUtils.save();
+
+        const id = this.getId();
+        if (id == null) {
+          BizException(ExceptionEnum.MESSAGE_ERROR, "id解析失败,请刷新页面");
+        }
+        await ElMessageBox.confirm("是否保存病历", "提示", {
+          type: "success",
+        });
+        const tmpSaveParams = {
+          emrDataElement: editor.getDataElements("business", false, true),
+          emrDocumentId: id,
+          document: { document: editor!.getDocument() },
+        };
+        updateFile(tmpSaveParams);
+      } finally {
+        store.isSave = false;
+      }
+    },
+    async del() {
+      if (store.currentEditParams.emrDocumentId === null) {
+        BizException(ExceptionEnum.MESSAGE_ERROR, "请先选择病历");
+      }
+      await ElMessageBox.confirm("是否删除病历,可在回收站找回", "提示", {
+        type: "error",
+      });
+      const id = this.getId();
+      delFile(id).then(res => {
+        mutation.queryPatientEmr();
+      });
+    },
+  };
+
+  const emrEventFun = {
+    componentClick: (evt, view) => {
+      const eleInfo = view.getAttribute("element");
+      caUtils.componentClick(evt, view);
+      isDev && eleInfo && console.log(eleInfo);
+    },
+  };
+
+  /**
+   * 代理病历的方法,防止病历还没有加载成功就执行了一些方法
+   */
+  const emrMutation = new Proxy(tmpEmrFun, {
+    get(target: typeof tmpEmrFun, prop: string | symbol, receiver: any) {
+      const original = target[prop];
+
+      // 如果不是函数,直接返回
+      if (typeof original !== "function") {
+        return original;
+      }
+
+      // 返回一个函数,确保正确的this绑定
+      return function (...args: any[]) {
+        if (!editor) {
+          xcMessage.error("病历还没有准备好。");
+          return false;
+        }
+        // 使用apply确保正确的this上下文
+        return original.apply(tmpEmrFun, args);
+      };
+    },
+  });
+
+  const useQueryEmr = queryEmrPatientInfo(store);
+
+  onMounted(async () => {
+    await useUserStore().getUserInfo;
+    const userInfo = useUserStore().userInfo;
+    const url = XEUtils.parseUrl(window.location.href);
+
+    store.urlParams = JSON.parse(window.atob(url.searchQuery.params));
+
+    if (userInfo.code !== store.urlParams.userIdCode) {
+      const url = XEUtils.parseUrl(window.location.href);
+      xcMessage.error("账号不一致请重新登录");
+      localStorage.clear();
+      // @ts-ignore
+      await router.push(`/login?redirect=${url?.path}`);
+      return;
+    } else {
+      queryMzPatientInfo(store.urlParams).then(res => {
+        store.patientInfo = res;
+        store.prepare = true;
+
+        useQueryEmr.query();
+      });
+    }
+  });
+
+  return {
+    store,
+    mutation,
+    userInfo,
+    emrMutation,
+    emrEventFun,
+  };
+};
+
+export const useMzEmrStoreKey = "useMzEmrStoreKey";
+export type UseMzEmrStoreType = ReturnType<typeof useMzEmrStore>;

+ 100 - 0
src/views/mz-emr/emr-v2/index.vue

@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import {
+  MzEmrTag,
+  useMzEmrStore,
+  useMzEmrStoreKey,
+  VueFile,
+} from "@/views/mz-emr/emr-v2/index";
+
+const root = useMzEmrStore();
+provide(useMzEmrStoreKey, root);
+
+const mainEditStyle = () => {
+  if (MzEmrTag.当前 === root.store.emrTag) {
+    return {
+      display: "block",
+    };
+  }
+  if (MzEmrTag.同时打开 === root.store.emrTag) {
+    return {
+      display: "block",
+      width: "50%",
+    };
+  }
+  return {
+    display: "none",
+  };
+};
+
+const historyStyle = () => {
+  if (MzEmrTag.历史 === root.store.emrTag) {
+    return {
+      display: "block",
+    };
+  }
+  if (MzEmrTag.同时打开 === root.store.emrTag) {
+    return {
+      display: "block",
+      width: "50%",
+    };
+  }
+  return {
+    display: "none",
+  };
+};
+</script>
+
+<template>
+  <div class="layout_container">
+    <header style="margin: 0">
+      <VueFile.MzEmrFun />
+    </header>
+    <div>
+      <VueFile.MzHeader />
+    </div>
+    <div
+      class="layout_main layout_container layout-horizontal"
+      v-if="root.store.prepare"
+    >
+      <aside>
+        <VueFile.MzEmrTemplate />
+      </aside>
+      <div class="layout_main layout_container">
+        <header class="mz-emr-tag">
+          <div
+            v-for="item in MzEmrTag"
+            class="mz-emr-tag--item"
+            :class="{ active: root.store.emrTag === item }"
+            @click="root.store.emrTag = item"
+          >
+            {{ item }}
+          </div>
+        </header>
+        <div class="layout_main" style="display: flex">
+          <VueFile.MzEditorMain :style="mainEditStyle()" />
+          <VueFile.MzEditorHistory :style="historyStyle()" />
+        </div>
+      </div>
+      <div style="width: 120px"></div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+.mz-emr-tag {
+  margin: 0 !important;
+  border: 1px solid var(--el-border-color);
+  line-height: 30px;
+  display: flex;
+
+  .mz-emr-tag--item {
+    padding: 0 10px;
+    cursor: pointer;
+    user-select: none;
+
+    &.active {
+      border-bottom: 2px solid var(--el-color-primary);
+    }
+  }
+}
+</style>

+ 145 - 0
src/views/mz-emr/emr-v2/useEmrCaSign.tsx

@@ -0,0 +1,145 @@
+import { EditType } from "@/utils/emr/edit";
+import { getInternalByCode, getViewById } from "@/utils/emr/emrUtils";
+import { useUserStore } from "@/pinia/user-store";
+import XEUtils from "xe-utils";
+import { CaSendParams, sendBatchByCode, sendByCode } from "@/api/ca/ca-api";
+import { ElMessageBox, ElNotification } from "element-plus";
+import { useDialog } from "@/components/cy/CyDialog/index";
+import { defineAsyncComponent } from "vue";
+
+const GenerateSignature = defineAsyncComponent(
+  () =>
+    import(
+      "@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/components/GenerateSignature.vue"
+    )
+);
+
+export enum CaSignReturn {
+  CA_SIGN_SUCCESS,
+  CA_SIGN_FAIL,
+  CA_SIGN_NO_DATA,
+}
+
+export function useEmrCaSignUtils() {
+  const us = useUserStore().userInfo;
+
+  let editor: EditType;
+  let notification = {
+    close: XEUtils.noop,
+  };
+
+  function oldSign() {
+    const signatureControl = getInternalByCode(editor, "自动签名", "business");
+    signatureControl.forEach(item => {
+      const element = getViewById(editor, item.id);
+      element.view.sign([
+        {
+          name: us.name,
+          code: us.code,
+          signature: `/doctorSignatureImage/${us.code}.png`,
+        },
+      ]);
+    });
+  }
+
+  async function caSign() {
+    notification = ElNotification({
+      type: "info",
+      duration: 0,
+      message: "正在生成签名,请稍后,请打开免密签名,并且ca签名是登录状态",
+    });
+    const signatureControl = getInternalByCode(editor, "编辑者CA签名");
+    // 如果没有这个数据源就不
+    if (
+      !XEUtils.isArray(signatureControl) ||
+      (signatureControl as any[]).length === 0
+    ) {
+      return CaSignReturn.CA_SIGN_NO_DATA;
+    }
+    const data: CaSendParams = {
+      msg: "电子病历系统签名",
+      desc: `该信息由门诊病历系统发送,病历文书id${editor.documentData._id}`,
+      id: us.code,
+      count: signatureControl?.length ?? 0,
+    };
+    const result = await sendBatchByCode(data).catch(() => {
+      return [];
+    });
+
+    if (result.length == 0) {
+      return CaSignReturn.CA_SIGN_FAIL;
+    }
+    signatureControl.forEach((signature, index) => {
+      const element = getViewById(editor, signature.id);
+      const emrData = result[index];
+      element.view.sign([emrData]);
+    });
+    return CaSignReturn.CA_SIGN_SUCCESS;
+  }
+
+  async function save() {
+    notification.close();
+    oldSign();
+    const tmp = await caSign();
+    notification.close();
+    if (tmp === CaSignReturn.CA_SIGN_FAIL) {
+      await ElMessageBox.alert(
+        "生成自己的签名失败,请检查是否开启了免签功能,或者云医签APP是否为登录状态,可重新保存病历触发签名功能。",
+        "签名失败",
+        {
+          type: "warning",
+        }
+      );
+    }
+    return tmp;
+  }
+
+  async function 授权CA签名(evt, view, eleInfo) {
+    if (eleInfo?.code?.internal !== "授权CA签名") {
+      return;
+    }
+    const res = await useDialog(GenerateSignature, {
+      dialogProps: {
+        title: "授权签名",
+      },
+    });
+    const code = res.code;
+    const msg = `该信息由门诊病历系统发送,病历文书id${editor.documentData._id}`;
+    const caData = await sendByCode({
+      id: code,
+      msg: msg,
+      desc: msg,
+    });
+    view.sign([caData]);
+  }
+
+  async function 老授权签名(evt, view, eleInfo) {
+    if (eleInfo?.code?.internal !== "授权签名") {
+      return;
+    }
+    const res = await useDialog(GenerateSignature, {
+      dialogProps: {
+        title: "授权签名",
+      },
+    });
+    view.sign([
+      {
+        name: res.name,
+        code: res.code,
+        signature: `/doctorSignatureImage/${res.code}.png`,
+      },
+    ]);
+  }
+
+  return {
+    install(e: EditType) {
+      editor = e;
+    },
+    save,
+    componentClick(evt, view) {
+      const eleInfo = view.getAttribute("element");
+      授权CA签名(evt, view, eleInfo);
+      老授权签名(evt, view, eleInfo);
+    },
+  };
+}