Browse Source

基本完成归档的功能

xiaochan 11 months ago
parent
commit
74d829b2de

+ 64 - 0
src/api/archive/archive-api.ts

@@ -27,3 +27,67 @@ export function getPatientArchives(patNo: string, times = 0) {
     params: { patNo, times },
   });
 }
+
+export function submitTask(data: { patNo: string; times: number }[]) {
+  return requestV2({
+    url: "/thyy/archive/submitTask",
+    method: "POST",
+    data,
+  });
+}
+
+export function currentOrganize(patNo: string, times: number) {
+  return requestV2<string>({
+    url: "/thyy/archive/currentOrganize",
+    method: "get",
+    params: { patNo, times },
+  });
+}
+
+export function organizeFiles(patNo: string, times: number) {
+  return requestV2({
+    url: "/thyy/archive/organizeFiles",
+    method: "get",
+    params: { patNo, times },
+  });
+}
+
+export function exitOrganizeFiles(patNo: string, times: number) {
+  return requestV2({
+    url: "/thyy/archive/exitOrganizeFiles",
+    method: "get",
+    params: { patNo, times },
+  });
+}
+
+export function sort(data) {
+  return requestV2({
+    url: "/thyy/archive/sort",
+    method: "post",
+    data,
+  });
+}
+
+export function delFile(data: PatientArchive) {
+  return requestV2({
+    url: "/thyy/archive/delFile",
+    method: "post",
+    data,
+  });
+}
+
+export function addDir(data) {
+  return requestV2({
+    url: "/thyy/archive/addDir",
+    method: "post",
+    data,
+  });
+}
+
+export function rename(id, name) {
+  return requestV2({
+    url: "/thyy/archive/rename",
+    method: "get",
+    params: { id, name },
+  });
+}

+ 72 - 66
src/api/inpatient/patient.js

@@ -1,105 +1,111 @@
-import request from '../../utils/request'
+import request from "../../utils/request";
 
 export function getOverView(ward, viewBabies = true) {
-    return request({
-        url: '/patient/getOverView',
-        method: 'get',
-        params: {ward, viewBabies},
-    })
+  return request({
+    url: "/patient/getOverView",
+    method: "get",
+    params: { ward, viewBabies },
+  });
 }
 
 export function getPatientInfo(inpatientNo) {
-    return request({
-        url: '/patient/getInfo',
-        method: 'get',
-        params: {inpatientNo},
-    })
+  return request({
+    url: "/patient/getInfo",
+    method: "get",
+    params: { inpatientNo },
+  });
 }
 
 export function getDrgPatInfo(data) {
-    return request({
-        url: '/patient/getDrgPatInfo',
-        method: 'post',
-        data
-    })
+  return request({
+    url: "/patient/getDrgPatInfo",
+    method: "post",
+    data,
+  });
 }
 
+/**
+ *
+ * @param patNo
+ * @param times
+ * @return {Promise<{inpatientNo:string,admissTimes: number}>}
+ */
 export function getPatientAll(patNo, times) {
-    return request({
-        url: '/patient/getPatientAll',
-        method: 'get',
-        params: {patNo, times},
-    })
+  return request({
+    url: "/patient/getPatientAll",
+    method: "get",
+    params: { patNo, times },
+  });
 }
 
 export function getDisPatient(patNo, times) {
-    return request({
-        url: '/patient/getDisPatient',
-        method: 'get',
-        params: {patNo, times},
-    })
+  return request({
+    url: "/patient/getDisPatient",
+    method: "get",
+    params: { patNo, times },
+  });
 }
 
 export function getDisDiag(inpatientNo, admissTimes) {
-    return request({
-        url: '/patient/getDisDiag',
-        method: 'get',
-        params: {inpatientNo, admissTimes},
-    })
+  return request({
+    url: "/patient/getDisDiag",
+    method: "get",
+    params: { inpatientNo, admissTimes },
+  });
 }
 
 export function fetchNotUploadedFees(data) {
-    return request({
-        url: '/patient/getNotUploadedFees',
-        method: 'post',
-        data,
-    })
+  return request({
+    url: "/patient/getNotUploadedFees",
+    method: "post",
+    data,
+  });
 }
 
 export function getIdCardInfo(inpatientNo, admissTimes) {
-    return request({
-        url: '/patient/getIdCardInfo',
-        method: 'get',
-        params: {inpatientNo, admissTimes},
-    })
+  return request({
+    url: "/patient/getIdCardInfo",
+    method: "get",
+    params: { inpatientNo, admissTimes },
+  });
 }
 
 export function submitSiPatientInfo(data) {
-    return request({
-        url: '/patient/submitSiPatientInfo',
-        method: 'post',
-        data,
-    })
+  return request({
+    url: "/patient/submitSiPatientInfo",
+    method: "post",
+    data,
+  });
 }
 
 export function getZyInYbDiags(inpatientNo, admissTimes) {
-    return request({
-        url: '/patient/getZyInYbDiags',
-        method: 'get',
-        params: {inpatientNo, admissTimes},
-    })
+  return request({
+    url: "/patient/getZyInYbDiags",
+    method: "get",
+    params: { inpatientNo, admissTimes },
+  });
 }
 
 export function saveZyInYbDiags(data) {
-    return request({
-        url: '/patient/saveSiZyInDiags',
-        method: 'post',
-        data,
-    })
+  return request({
+    url: "/patient/saveSiZyInDiags",
+    method: "post",
+    data,
+  });
 }
 
 export function genDismissActOrder(data) {
-    return request({
-        url: '/patient/genDismissActOrder',
-        method: 'post',
-        data,
-    })
+  return request({
+    url: "/patient/genDismissActOrder",
+    method: "post",
+    data,
+  });
 }
 
 export function receiveAndRecalculateCost(data) {
-    return request({
-        url: '/patient/receiveAndRecalculateCost',
-        method: 'post',
-        data,
-    })
+  return request({
+    url: "/patient/receiveAndRecalculateCost",
+    method: "post",
+    data,
+  });
 }

+ 1 - 1
src/components/cy/CyDialog/index.ts

@@ -42,7 +42,7 @@ type FirstParam<T> =
 
 export interface DialogOptions<P = Component> {
   dialogProps: DialogProps;
-  params?: FirstParam<P>;
+  params?: any;
   showCancel?: boolean;
   showConfirm?: boolean;
   showFooter?: boolean;

+ 4 - 1
src/components/cy/cy-el-tabs/TabPaneResizer.vue

@@ -15,9 +15,12 @@ const root = inject(cyElTabsKey);
 
 const bindData = computed(() => {
   const reverse = root.props.value.tabPosition === "right";
+  const direction = ["left", "right"].includes(root.props.value.tabPosition)
+    ? "x"
+    : "y";
 
   return {
-    direction: "x",
+    direction,
     reverse,
     value: toNumber(props.value ?? root.props.value.value),
     min: toNumber(props.min ?? root.props.value.min),

+ 41 - 3
src/components/cy/cy-el-tabs/TabsResizer.vue

@@ -6,16 +6,18 @@ import useCyTabs, { cyElTabsKey } from "@/components/cy/cy-el-tabs/index";
 const props = withDefaults(
   defineProps<{
     modelValue?: string | number;
-    tabPosition?: "left" | "right";
+    tabPosition?: "left" | "right" | "bottom";
     min?: string | number;
     max?: string | number;
     value?: string | number;
+    visible?: boolean;
   }>(),
   {
     tabPosition: "left",
     value: "400",
     min: "100",
     max: "500",
+    visible: undefined,
   }
 );
 
@@ -23,10 +25,28 @@ const store = useCyTabs(toRef(props));
 
 provide(cyElTabsKey, store);
 
-const emits = defineEmits(["update:modelValue"]);
+const emits = defineEmits(["update:modelValue", "update:visible"]);
 
 const model = useVModel(props, "modelValue", emits);
-const show = ref(true);
+const visible = ref(true);
+const hasVisible = typeof props.visible !== "undefined";
+
+const show = computed({
+  get() {
+    if (hasVisible) {
+      return props.visible;
+    } else {
+      return visible.value;
+    }
+  },
+  set(value) {
+    if (hasVisible) {
+      emits("update:visible", value);
+    } else {
+      visible.value = value;
+    }
+  },
+});
 
 const showClass = computed(() => {
   return show.value ? "" : "cy-el-tabs-hide";
@@ -98,4 +118,22 @@ function handleClick(val) {
     display: none;
   }
 }
+
+.el-tabs--top.cy-el-tabs,
+.el-tabs--bottom.cy-el-tabs {
+  > .el-tabs__content {
+    padding: 0 5px 5px;
+  }
+}
+
+.el-tabs--top.cy-el-tabs-hide,
+.el-tabs--bottom.cy-el-tabs-hide {
+  > .el-tabs__header {
+    margin: 0;
+  }
+
+  > .el-tabs__content {
+    display: none;
+  }
+}
 </style>

+ 1 - 2
src/components/cy/magic-resizer.vue

@@ -57,8 +57,7 @@ export default {
 
       if (documentIframe) {
         const mask = document.createElement("div");
-        mask.style.cssText =
-          "position: absolute;top: 0;left: 0;width: 100vw;height: 100vh;z-index: 9999;cursor: col-resize";
+        mask.style.cssText = `position: absolute;top: 0;left: 0;width: 100vw;height: 100vh;z-index: 9999;cursor: ${horizontal ? "col-resize" : "row-resize"}`;
         mask.setAttribute("id", "mousemoveMask");
         document.body.append(mask);
       }

+ 28 - 29
src/utils/xiaochan-element-plus.ts

@@ -1,37 +1,36 @@
-import {ElMessage, useNamespace} from "element-plus";
-import {ref} from 'vue'
+import { ElMessage, useNamespace } from "element-plus";
+import { ref } from "vue";
 
 function message(msg: string, type: string, isHtml: boolean) {
-    return ElMessage({
-        type: type,
-        // @ts-ignore
-        duration: 3500,
-        dangerouslyUseHTMLString: isHtml,
-        message: msg,
-        showClose: true,
-        grouping: true,
-    })
+  return ElMessage({
+    type: type,
+    // @ts-ignore
+    duration: 3500,
+    dangerouslyUseHTMLString: isHtml,
+    message: msg,
+    showClose: true,
+    grouping: true,
+  });
 }
 
 export const xcMessage = {
-    success: (msg: string, isHtml = false) => {
-        return message(msg, 'success', isHtml)
-    },
-    danger: (msg: string, isHtml = false) => {
-        return message(msg, 'danger', isHtml)
-    },
-    info: (msg: string, isHtml = false) => {
-        return message(msg, 'info', isHtml)
-    },
-    warning: (msg: string, isHtml = false) => {
-        return message(msg, 'warning', isHtml)
-    },
-    error: (msg: string, isHtml = false) => {
-        return message(msg, 'error', isHtml)
-    }
-}
+  success: (msg: string, isHtml = false) => {
+    return message(msg, "success", isHtml);
+  },
+  danger: (msg: string, isHtml = false) => {
+    return message(msg, "error", isHtml);
+  },
+  info: (msg: string, isHtml = false) => {
+    return message(msg, "info", isHtml);
+  },
+  warning: (msg: string, isHtml = false) => {
+    return message(msg, "warning", isHtml);
+  },
+  error: (msg: string, isHtml = false) => {
+    return message(msg, "error", isHtml);
+  },
+};
 
 export function useCyNamespace(name: string) {
-    return useNamespace(name, ref('cy'))
+  return useNamespace(name, ref("cy"));
 }
-

+ 128 - 14
src/views/archive/ArchiveAside.vue

@@ -1,11 +1,15 @@
 <script setup lang="tsx">
 import { Document, Files } from "@element-plus/icons-vue";
 import { useArchiveKey } from "@/views/archive/index";
-import { ElTree } from "element-plus";
-import MagicResizer from "@/components/cy/magic-resizer.vue";
+import { ElMessageBox, ElTree } from "element-plus";
+import * as archiveApi from "@/api/archive/archive-api";
 import { PatientArchive } from "@/api/archive/archive-api";
 import CyTreeInput from "@/components/cy/tree-input/src/CyTreeInput.vue";
 import ContextMenu from "@imengyu/vue3-context-menu";
+import TabsResizer from "@/components/cy/cy-el-tabs/TabsResizer.vue";
+import TabPaneResizer from "@/components/cy/cy-el-tabs/TabPaneResizer.vue";
+import { xcMessage } from "@/utils/xiaochan-element-plus";
+import XEUtils from "xe-utils";
 
 const root = inject(useArchiveKey);
 const treeRef = ref(ElTree);
@@ -16,6 +20,7 @@ const defaultProps = {
 };
 
 function handleNodeClick(node: PatientArchive) {
+  if (node.isDir) return;
   root.mutation.setPdfUrl(node.path);
 }
 
@@ -23,6 +28,86 @@ function allowDrop(draggingNode, dropNode, type) {
   return !(type === "inner" && dropNode.data.isDir === false);
 }
 
+function innerSort(value: PatientArchive[], parentId) {
+  const tmp = XEUtils.map(value, (item, index) => {
+    item.parentId = parentId;
+
+    return {
+      isDir: item.isDir,
+      id: item.id,
+      parentId: parentId,
+      sort: index,
+    };
+  });
+
+  archiveApi
+    .sort({
+      archives: tmp,
+      patNo: root.store.patInfo.inpatientNo,
+      times: root.store.patInfo.admissTimes,
+    })
+    .then(res => {
+      xcMessage.success("排序成功。");
+    });
+}
+
+async function nodeDrop(currentNode: any, dropNode: any, type, event) {
+  await nextTick();
+
+  if (type === "inner") {
+    innerSort(dropNode.data.children, dropNode.data.id);
+  } else {
+    const isarr = XEUtils.isArray(dropNode.parent.data);
+
+    innerSort(
+      isarr ? dropNode.parent.data : dropNode.parent.data.children,
+      isarr ? null : dropNode.parent.data.id
+    );
+  }
+}
+
+async function addDir(parentId: string) {
+  const name = await ElMessageBox.prompt("文件夹名称", "新增", {
+    type: "info",
+    inputErrorMessage: "名称不能为空",
+    inputPattern: /\S/,
+  }).then(({ value }) => value);
+
+  const tmpData = {
+    name: name,
+    isDir: true,
+    parentId: parentId,
+    id: null,
+    type: 3,
+    children: [],
+    ...root.mutation.getPatNoAndTimes(),
+  } as PatientArchive;
+  await archiveApi.addDir(tmpData);
+  await root.mutation.getData();
+}
+
+async function removeFile(data: PatientArchive) {
+  data.patNo = root.store.patInfo.inpatientNo;
+  data.times = root.store.patInfo.admissTimes;
+
+  await archiveApi.delFile({
+    ...data,
+    ...root.mutation.getPatNoAndTimes(),
+  });
+  await root.mutation.getData();
+}
+
+async function renameFile(data: PatientArchive) {
+  const name = await ElMessageBox.prompt("文件夹名称", "新增", {
+    type: "info",
+    inputErrorMessage: "名称不能为空",
+    inputPattern: /\S/,
+  }).then(({ value }) => value);
+
+  await archiveApi.rename(data.id, name);
+  data.name = name;
+}
+
 function contextmenu(e: any, data: PatientArchive, _node: any, _n: any) {
   e.preventDefault();
   ContextMenu.showContextMenu({
@@ -33,23 +118,51 @@ function contextmenu(e: any, data: PatientArchive, _node: any, _n: any) {
         label: "新增文件夹",
         disabled: !data.isDir,
         onClick: () => {
-          console.log(data);
+          addDir(data.id);
+        },
+      },
+      {
+        label: "重命名",
+        disabled: data.id === null,
+        onClick: () => {
+          renameFile(data);
+        },
+      },
+      {
+        label: "上传文件",
+        disabled: !data.isDir,
+        onClick: () => {
+          root.mutation.uploadFiles(data.id);
+        },
+      },
+      {
+        label: "删除",
+        disabled: data.id === null || data.id === "tmpDir",
+        onClick: () => {
+          removeFile(data as PatientArchive);
         },
       },
     ],
   });
 }
+
+function tabContestMenu(event) {
+  //@ts-ignore
+  contextmenu(event, { isDir: true, id: null }, null, null);
+}
 </script>
 
 <template>
-  <div style="padding: 10px">
-    <magic-resizer
-      :value="300"
-      :max="600"
-      :min="300"
-      direction="x"
-      class="archive-resizer"
-      @contextmenu="contextmenu($event, { isDir: true, id: null }, null, null)"
+  <tabs-resizer
+    :value="300"
+    :max="600"
+    :min="100"
+    v-model="root.store.asideValue"
+  >
+    <TabPaneResizer
+      label="目录"
+      name="archive"
+      @contextmenu="tabContestMenu($event)"
     >
       <cy-tree-input :tree-ref="treeRef" :remote-method="root.mutation.getData">
         <template #default="{ handelFilter, treeProps }">
@@ -61,10 +174,12 @@ function contextmenu(e: any, data: PatientArchive, _node: any, _n: any) {
             draggable
             :allow-drop="allowDrop"
             :filter-node-method="handelFilter"
+            default-expand-all
             node-key="id"
             :props="defaultProps"
             @node-contextmenu="contextmenu"
             @node-click="handleNodeClick"
+            @nodeDrop="nodeDrop"
           >
             <template #default="{ data }">
               <el-icon>
@@ -76,8 +191,8 @@ function contextmenu(e: any, data: PatientArchive, _node: any, _n: any) {
           </el-tree>
         </template>
       </cy-tree-input>
-    </magic-resizer>
-  </div>
+    </TabPaneResizer>
+  </tabs-resizer>
 </template>
 
 <style lang="scss">
@@ -87,7 +202,6 @@ function contextmenu(e: any, data: PatientArchive, _node: any, _n: any) {
 }
 
 .archive_aside {
-  padding: 10px;
   background: white;
   width: 100%;
 }

+ 83 - 0
src/views/archive/ArchiveFooter.vue

@@ -0,0 +1,83 @@
+<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 { useArchiveKey } from "@/views/archive/index";
+import DisplayArchiveLog from "@/views/archive/DisplayArchiveLog.vue";
+
+const root = inject(useArchiveKey);
+
+const setRef = (el: HTMLDivElement) => {
+  // @ts-ignore
+  root.store.footerRef = shallowRef<HTMLDivElement>(el);
+};
+</script>
+
+<template>
+  <TabsResizer
+    tab-position="bottom"
+    :max="700"
+    :min="150"
+    :value="250"
+    v-model:visible="root.store.footerVisible"
+    v-model="root.store.footerValue"
+    class="archive-footer"
+  >
+    <TabPaneResizer label="运行日志" name="footer">
+      <div class="archive_log-content">
+        <div class="archive_log-aside">
+          <el-button
+            icon="Delete"
+            size="default"
+            text
+            @click="root.mutation.clearLog()"
+            title="清空日志"
+          />
+          <el-button
+            icon="Top"
+            size="default"
+            text
+            @click="root.mutation.logScroll(true)"
+            title="滚动到最上"
+          />
+          <el-button
+            icon="Bottom"
+            size="default"
+            text
+            @click="root.mutation.logScroll()"
+            title="滚动到最下"
+          />
+        </div>
+        <DisplayArchiveLog
+          :data="root.store.logMessage"
+          :ref="el => setRef(el)"
+        />
+      </div>
+    </TabPaneResizer>
+  </TabsResizer>
+</template>
+
+<style lang="scss">
+.archive-footer {
+  .el-tabs__content {
+    padding: 0 !important;
+  }
+}
+
+.archive_log-aside {
+  width: max-content;
+  writing-mode: vertical-rl;
+  text-orientation: upright;
+  user-select: none;
+  border-right: 1px solid var(--el-border-color);
+
+  .el-button {
+    margin: 0;
+  }
+}
+
+.archive_log-content {
+  display: flex;
+  height: 100%;
+  width: 100%;
+}
+</style>

+ 5 - 6
src/views/archive/ArchiveMain.vue

@@ -6,7 +6,11 @@ const root = inject(useArchiveKey);
 
 <template>
   <div class="archive_main">
-    <iframe :src="root.store.pdfUrl" />
+    <iframe
+      :src="root.store.pdfUrl"
+      :ref="el => (root.store.mainIframe = el)"
+      class="layout_full_iframe"
+    />
   </div>
 </template>
 
@@ -15,10 +19,5 @@ const root = inject(useArchiveKey);
   background: white;
   width: 100%;
   height: 100%;
-
-  iframe {
-    width: 100%;
-    height: 100%;
-  }
 }
 </style>

+ 77 - 0
src/views/archive/ArchiveUpload.vue

@@ -0,0 +1,77 @@
+<script setup lang="ts">
+import CyDialogContainer from "@/components/cy/CyDialog/CyDialogContainer.vue";
+import { UploadFilled } from "@element-plus/icons-vue";
+import { UploadFile, UploadFiles } from "element-plus";
+
+const props = defineProps<{ patNo?: string; times?: number; parent: string }>();
+const emits = defineEmits(["cyDialogConfirm", "cyDialogCancel"]);
+
+const tmpData = ref([]);
+
+function confirm() {
+  if (tmpData.value.length === 0) {
+    emits("cyDialogCancel");
+    return;
+  }
+  emits("cyDialogConfirm", tmpData.value);
+}
+
+const bindUpload = {
+  action: `${import.meta.env.VITE_BASE_URL}/thyy/archive/upload`,
+  headers: {
+    token: localStorage.getItem("token"),
+  },
+  accept: ".pdf",
+  onSuccess: async (
+    response: any,
+    uploadFile: UploadFile,
+    uploadFiles: UploadFiles
+  ) => {
+    if (response.code === 200) {
+      tmpData.value.push(response.data);
+      uploadFile.status = "success";
+    } else {
+      uploadFile.status = "fail";
+    }
+  },
+  beforeRemove: (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    return uploadFile.status === "fail";
+  },
+  data: () => {
+    return {
+      ...props,
+    };
+  },
+};
+</script>
+
+<template>
+  <CyDialogContainer @confirm="confirm">
+    <el-upload class="archive_upload" drag multiple v-bind="bindUpload">
+      <el-icon class="el-icon--upload">
+        <upload-filled />
+      </el-icon>
+      <div class="el-upload__text">将文件拖放到此处或 <em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip">只能上传pdf文件,可多选</div>
+      </template>
+    </el-upload>
+  </CyDialogContainer>
+</template>
+
+<style lang="scss">
+.archive_upload {
+  height: 400px;
+  display: flex;
+  flex-direction: column;
+
+  > div {
+    height: max-content;
+  }
+
+  > .el-upload-list {
+    flex: 1;
+    overflow: auto;
+  }
+}
+</style>

+ 64 - 0
src/views/archive/DisplayArchiveLog.vue

@@ -0,0 +1,64 @@
+<script setup lang="ts">
+const props = defineProps<{
+  data: string[][];
+}>();
+
+const refVal = ref<HTMLDivElement>();
+
+defineExpose({
+  scroll(top = true) {
+    refVal.value.scroll({
+      top: top ? 0 : refVal.value.scrollHeight,
+      left: 0,
+      behavior: "smooth",
+    });
+  },
+});
+</script>
+
+<template>
+  <div class="archive_log-pre" ref="refVal">
+    <pre
+      v-for="item in props.data"
+    >[{{ item?.[0] }}] <span :class="`archive__log-span-${item?.[1]}`">[{{
+        item?.[1]
+      }}]</span> [{{ item?.[2] }}] <span class="archive__log-span-cyan">[{{ item?.[3] }}]</span> {{ item?.[4] }}</pre>
+  </div>
+</template>
+
+<style lang="scss">
+.archive_log-pre {
+  background: #f2f2f2;
+  flex: 1;
+  width: 0;
+  overflow: auto;
+
+  pre {
+    font-family:
+      JetBrainsMono,
+      Consolas,
+      Courier New,
+      \5fae\8f6f\96c5\9ed1,
+      serif;
+    width: 100%;
+    margin: 0;
+    line-height: 20px;
+  }
+}
+
+.archive__log-span-cyan {
+  color: #00cccc;
+}
+
+.archive__log-span-INFO {
+  color: #00cd00;
+}
+
+.archive__log-span-WARN {
+  color: #a66f00;
+}
+
+.archive__log-span-ERROR {
+  color: #cd0000;
+}
+</style>

+ 137 - 11
src/views/archive/index.ts

@@ -1,13 +1,57 @@
-import type { InjectionKey } from "vue";
+import type { InjectionKey, ShallowRef } from "vue";
 import { getPatientAll } from "@/api/inpatient/patient";
-import { getPatientArchives, PatientArchive } from "@/api/archive/archive-api";
+import * as archiveApi from "@/api/archive/archive-api";
+import {
+  getPatientArchives,
+  PatientArchive,
+  submitTask,
+} from "@/api/archive/archive-api";
 import XEUtils from "xe-utils";
+import { useWebSocket } from "@vueuse/core";
+import { useUserStore } from "@/pinia/user-store";
+import { ElNotification } from "element-plus";
+import { useDialog } from "@/components/cy/CyDialog/index";
+import ArchiveUpload from "./ArchiveUpload.vue";
 
 type PatInfo = {
+  inpatientNo: string;
+  admissTimes: number;
   admissDate: string;
   disDate: string;
 };
 
+type Socket = {
+  [key: string]: (value: any) => void;
+};
+
+const useSocket = (cb: Socket) => {
+  const socketUrl = ref();
+
+  const { data, open } = useWebSocket(socketUrl, {
+    immediate: false,
+  });
+
+  watch(
+    () => data.value,
+    () => {
+      try {
+        const json = JSON.parse(data.value);
+        cb[json.code]?.(json.data);
+      } catch (e) {
+        console.error(e);
+      }
+    },
+    { immediate: false, deep: true }
+  );
+
+  return {
+    setUrl(url: string) {
+      socketUrl.value = url;
+      open();
+    },
+  };
+};
+
 export const useArchive = () => {
   const store = reactive({
     patNo: "",
@@ -15,35 +59,117 @@ export const useArchive = () => {
     patInfo: {} as PatInfo,
     treeList: [] as PatientArchive[],
     pdfUrl: "",
+    asideValue: "archive",
+    footerValue: "footer",
+    footerVisible: true,
+    logMessage: [],
+    footerRef: null as ShallowRef<{ scroll: (val: boolean) => void }>,
+    mainIframe: null as HTMLIFrameElement,
+    mainTabsValue: "pdf",
+  });
+
+  const socket = useSocket({
+    log: data => {
+      store.logMessage.push(JSON.parse(data));
+      store.footerVisible = true;
+      mutation.logScroll(false);
+    },
+    change: id => {
+      if (useUserStore().userInfo.code !== id) {
+        ElNotification.success({
+          title: "文件有变化",
+          message: "自动刷新目录",
+        });
+        mutation.getData();
+      }
+    },
   });
 
   const mutation = {
     async getData() {
       store.treeList = await getPatientArchives(store.patNo, store.times);
     },
-    setPatInfo(patInfo: string) {
+    async setPatInfo(patInfo: string) {
       const split = patInfo.split("_");
       store.patNo = split[0];
       store.times = XEUtils.toNumber(split[1]);
-      getPatientAll(store.patNo, store.times).then(res => {
-        store.patInfo = res as any;
-        this.getData();
-      });
+      socket.setUrl(
+        `${import.meta.env.VITE_SOCKET_URL}archive-${store.patNo}-${store.times}-${useUserStore().userInfo.code}`
+      );
+      const res = await getPatientAll(store.patNo, store.times);
+      store.patInfo = res as any;
+      this.getData();
     },
     setPdfUrl(url: string) {
-      store.pdfUrl = url;
+      store.mainIframe?.contentWindow?.scrollTo(0, 0);
+      store.pdfUrl = "/thyyemrpdfserver" + url;
     },
-    sort() {
-      function forEach(data: PatientArchive[]) {
+    async sort() {
+      function forEach(
+        data: PatientArchive[],
+        fatherLevel: PatientArchive | null = null
+      ) {
         data.forEach((item, index) => {
+          if (fatherLevel) {
+            item.parentId = fatherLevel.id;
+          } else {
+            item.parentId = null;
+          }
+
           item.sort = index;
           if (item.children !== null && item.children.length > 0) {
-            forEach(item.children);
+            forEach(item.children, item);
           }
         });
       }
 
       forEach(store.treeList);
+
+      const tmp = {
+        patNo: store.patInfo.inpatientNo,
+        times: store.patInfo.admissTimes,
+        archives: mutation.toTreeArray(),
+      };
+      await archiveApi.sort(tmp);
+    },
+    toTreeArray(): PatientArchive[] {
+      return XEUtils.toTreeArray(XEUtils.cloneDeep(store.treeList), {
+        clear: true,
+      });
+    },
+    archiveTask() {
+      submitTask([{ patNo: store.patNo, times: store.times }]);
+    },
+    clearLog() {
+      store.logMessage = [];
+    },
+    async logScroll(top = false) {
+      await nextTick();
+      store.footerRef?.scroll?.(top);
+    },
+    async uploadFiles(parent: string) {
+      await useDialog(ArchiveUpload, {
+        dialogProps: {
+          title: "上传文件",
+          closeOnClickModal: false,
+          showClose: false,
+          closeOnPressEscape: false,
+        },
+        showCancel: false,
+        params: {
+          patNo: store.patInfo.inpatientNo,
+          times: store.patInfo.admissTimes,
+          parent,
+        },
+      });
+
+      await mutation.getData();
+    },
+    getPatNoAndTimes() {
+      return {
+        patNo: store.patInfo.inpatientNo,
+        times: store.patInfo.admissTimes,
+      };
     },
   };
 

+ 7 - 2
src/views/archive/index.vue

@@ -5,6 +5,7 @@ import router from "@/router";
 import XEUtils from "xe-utils";
 import { stringNotBlank } from "@/utils/blank-utils";
 import ArchiveMain from "@/views/archive/ArchiveMain.vue";
+import ArchiveFooter from "@/views/archive/ArchiveFooter.vue";
 
 const root = useArchive();
 
@@ -25,14 +26,18 @@ watch(
 <template>
   <div class="layout_container">
     <header class="archive_header">
-      <el-button @click="root.mutation.sort">确认排序</el-button>
+      <el-button @click="root.mutation.archiveTask" type="primary" icon="Files"
+        >生成pdf
+      </el-button>
+      <!--      <el-divider direction="vertical" />-->
     </header>
     <div class="layout_main layout_container layout-horizontal">
       <ArchiveAside />
-      <div class="layout_main" style="padding: 10px">
+      <div class="layout_main">
         <ArchiveMain />
       </div>
     </div>
+    <ArchiveFooter />
   </div>
 </template>