Jelajahi Sumber

修复路由跳转BUG

Huanyi 3 minggu lalu
induk
melakukan
d421a52659

+ 373 - 0
.specstory/history/2025-08-04_01-16Z-vue-js-页面跳转空白问题.md

@@ -0,0 +1,373 @@
+<!-- Generated by SpecStory -->
+
+# Vue.js 页面跳转空白问题 (2025-08-04 01:16Z)
+
+_**User**_
+
+@/parameter @/basicPublicTemplate @/nutritionEducationTemplate @index.vue 为什么只要加载了这个页面以后,其它的所有页面会全部变成一片空白?并且,从其他页面回来时也会出现这个一片空白的情况?
+
+---
+
+_**Assistant**_
+
+<think><details><summary>Thought Process</summary>用户反映了一个严重的问题:加载 `src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue` 页面后,其他所有页面都会变成空白,并且从其他页面回来时也会出现这个问题。
+
+这种问题通常是由以下几个原因引起的:
+
+1. JavaScript 错误导致整个应用崩溃
+2. 组件中的无限循环或死循环
+3. 内存泄漏
+4. 全局状态被错误修改
+5. 路由配置问题
+6. 组件卸载时没有正确清理资源
+
+让我先查看这个页面的代码,分析可能的问题。从linter错误来看,有一些导入模块找不到的问题,但这通常不会导致整个应用崩溃。
+
+从代码来看,我注意到几个可能的问题点:
+
+1. 在 `getList` 函数中,有一个遍历 `treeData.value` 的操作,但如果 `treeData` 的结构不正确,可能会导致问题
+2. `showTooltip` 和 `hideTooltip` 函数操作DOM,可能会有问题
+3. `onMounted` 中同时调用了 `getList()` 和 `getDeptList()`,可能存在竞态条件
+
+让我深入分析代码,特别关注可能导致全局问题的部分。</details></think>
+
+---
+
+我来分析这个问题。这种"一片空白"的情况通常是由JavaScript错误导致整个Vue应用崩溃造成的。让我检查一下代码中可能导致这个问题的原因。
+
+---
+
+
+
+---
+
+<think><details><summary>Thought Process</summary>看起来搜索没有找到相关的API文件。让我直接检查API目录结构,确认导入路径是否正确。</details></think>
+
+---
+
+<details>
+            <summary>Listed directory src/api/parameter • **1** results</summary>
+        
+| Name |
+|-------|
+| 📁 `basicPublicTemplate` |
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>让我继续查看basicPublicTemplate目录下的内容。</details></think>
+
+---
+
+<details>
+            <summary>Listed directory src/api/parameter/basicPublicTemplate • **3** results</summary>
+        
+| Name |
+|-------|
+| 📁 `nutritionEducationTemplate` |
+| 📁 `consultantResultTemplateType` |
+| 📁 `consultantResultTemplate` |
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>看起来目录结构是存在的。让我查看nutritionEducationTemplate目录下的具体文件。</details></think>
+
+---
+
+<details>
+            <summary>Listed directory src/api/parameter/basicPublicTemplate/nutritionEducationTemplate • **2** results</summary>
+        
+| Name |
+|-------|
+| 📄 `index.ts` |
+| 📄 `types.ts` |
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>好的,API文件是存在的。让我检查这些API文件的内容,看看是否有问题。同时也要检查路由配置和其他可能导致应用崩溃的问题。</details></think>
+
+---
+
+Read file: src/api/parameter/basicPublicTemplate/nutritionEducationTemplate/index.ts
+
+---
+
+Read file: src/api/parameter/basicPublicTemplate/nutritionEducationTemplate/types.ts
+
+---
+
+<think><details><summary>Thought Process</summary>API文件看起来是正常的。现在让我分析一下原始页面代码中可能导致应用崩溃的问题。我注意到几个潜在的问题:
+
+1. 在`getList`函数中,有一个遍历`treeData.value`的循环,但这个时候`treeData`可能还没有被正确初始化
+2. `onMounted`中同时调用了`getList()`和`getDeptList()`,存在竞态条件
+3. tooltip的DOM操作可能有问题
+
+让我检查一下路由配置和其他组件是否有问题。</details></think>
+
+---
+
+```bash
+echo "请检查浏览器开发者工具的Console选项卡是否有错误信息"
+```
+
+---
+
+<think><details><summary>Thought Process</summary>用户没有选择运行这个命令。我需要直接分析代码中的问题。让我仔细检查营养宣教模板页面的代码,找出可能导致应用崩溃的原因。
+
+从代码分析来看,我发现了一些潜在的问题:
+
+1. 在`getList`函数中,第166-183行,代码试图访问`treeData.value`并进行遍历,但是此时`getDeptList()`可能还没有完成执行,导致`treeData.value`为空或未定义
+2. `onMounted`中同时调用了两个异步函数,存在竞态条件
+3. tooltip的实现方式可能有问题
+
+让我提供修复方案。</details></think>
+
+---
+
+分析代码后,我发现了导致页面空白的几个关键问题。主要原因是代码中存在一些会导致JavaScript错误的问题:
+
+## 问题分析
+
+1. **竞态条件问题**: 在 `onMounted` 中同时调用了 `getList()` 和 `getDeptList()`,但 `getList()` 中尝试访问 `treeData.value`,而此时 `getDeptList()` 可能还没完成
+
+2. **空值访问问题**: 在 `getList()` 函数中直接遍历 `treeData.value`,没有进行空值检查
+
+3. **EducationTemplateVO类型缺少字段**: types文件中缺少页面中使用的一些字段定义
+
+让我修复这些问题:
+
+---
+
+<details><summary>Edit file: src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue</summary>
+
+修复竞态条件和空值访问问题,确保页面不会崩溃
+
+**Chunk 1**
+Lines added: 6, lines removed: 11
+
+```diff
+@@ -166,19 +166,14 @@
+  const getList = async () => {
+      loading.value = true;
++     try {
+          const res = await listEducationTemplate(queryParams.value);
+          educationTemplateList.value = res.rows;
+- 
+-     // 获取部门数据
+-     const deptMap = new Map();
+-     treeData.value.forEach(dept => {
+-         deptMap.set(dept.deptId, dept.deptName);
+-         if (dept.children) {
+-             dept.children.forEach(child => {
+-                 deptMap.set(child.deptId, child.deptName);
+-             });
+-         }
+-     });
+          total.value = res.total;
++     } catch (error) {
++         console.error('获取营养宣教模板列表失败:', error);
++         proxy?.$modal.msgError("获取数据失败");
++     } finally {
+          loading.value = false;
++     }
+  }
+  
+```
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>好的,我移除了`getList`中对`treeData.value`的不安全访问。现在让我修复`onMounted`中的竞态条件问题,确保先获取部门数据再获取列表数据。</details></think>
+
+---
+
+<details><summary>Edit file: src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue</summary>
+
+修复onMounted中的竞态条件,先获取部门数据再获取列表数据
+
+**Chunk 1**
+Lines added: 7, lines removed: 3
+
+```diff
+@@ -265,7 +265,11 @@
+  }
+  
+- onMounted(() => {
+-     getList();
+-     getDeptList();
++ onMounted(async () => {
++     try {
++         await getDeptList();
++         await getList();
++     } catch (error) {
++         console.error('页面初始化失败:', error);
++     }
+  });
+  </script>
+```
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>现在让我修复types文件,添加页面中使用的字段定义。</details></think>
+
+---
+
+<details><summary>Edit file: src/api/parameter/basicPublicTemplate/nutritionEducationTemplate/types.ts</summary>
+
+在EducationTemplateVO接口中添加页面使用的缺失字段
+
+**Chunk 1**
+Lines added: 30, lines removed: undefined
+
+```diff
+@@ -36,4 +36,34 @@
+       */
+      updateDept: number;
++ 
++     /**
++      * 创建时间
++      */
++     createTime: string;
++ 
++     /**
++      * 创建人姓名
++      */
++     createByUser: string;
++ 
++     /**
++      * 创建人科室
++      */
++     createDeptName: string;
++ 
++     /**
++      * 最后编辑时间
++      */
++     updateTime: string;
++ 
++     /**
++      * 最后编辑人姓名
++      */
++     updateByUser: string;
++ 
++     /**
++      * 最后编辑人科室
++      */
++     updateDeptName: string;
+  }
+  
+```
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>现在让我修复showTooltip函数中可能存在的DOM操作问题,确保它更安全。</details></think>
+
+---
+
+<details><summary>Edit file: src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue</summary>
+
+修复showTooltip函数,添加安全检查防止DOM操作错误
+
+**Chunk 1**
+Lines added: 2, lines removed: undefined
+
+```diff
+@@ -148,4 +148,6 @@
+  
+  const showTooltip = (event: MouseEvent, content: string) => {
++     if (!event.target || !content) return;
++     
+      const target = event.target as HTMLElement;
+      const rect = target.getBoundingClientRect();
+```
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>现在让我添加一个清理函数,确保组件卸载时正确清理tooltip。</details></think>
+
+---
+
+<details><summary>Edit file: src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue</summary>
+
+在onMounted后添加onUnmounted清理函数
+
+**Chunk 1**
+Lines added: 5, lines removed: undefined
+
+```diff
+@@ -275,4 +275,9 @@
+      }
+  });
++ 
++ onUnmounted(() => {
++     // 清理tooltip
++     hideTooltip();
++ });
+  </script>
+  
+```
+
+</details>
+
+---
+
+<think><details><summary>Thought Process</summary>
+
+
+
+现在让我检查一下是否有其他可能导致问题的代码片段,特别是删除和错误处理函数。</details></think>
+
+---
+
+<details><summary>Edit file: src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue</summary>
+
+修复handleDelete函数,添加更好的错误处理
+
+**Chunk 1**
+Lines added: 10, lines removed: 1
+
+```diff
+@@ -254,8 +254,17 @@
+  const handleDelete = async (row?: EducationTemplateVO) => {
+      const _ids = row?.id || ids.value;
+-     await proxy?.$modal.confirm('是否确认删除营养宣教模板编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
++     try {
++         await proxy?.$modal.confirm('是否确认删除营养宣教模板编号为"' + _ids + '"的数据项?');
+          await delEducationTemplate(_ids);
+          proxy?.$modal.msgSuccess("删除成功");
+          await getList();
++     } catch (error) {
++         console.error('删除失败:', error);
++         if (error !== 'cancel') {
++             proxy?.$modal.msgError("删除失败");
++         }
++     } finally {
++         loading.value = false;
++     }
+  }
+  
+```
+
+</details>
+
+---
+
+
+
+---
+
+
+
+---
+

+ 1 - 1
src/layout/components/Sidebar/Logo.vue

@@ -34,7 +34,7 @@ defineProps({
   }
 });
 
-const title = ref('RuoYi-Vue-Plus');
+const title = ref('智能营养管理系统');
 const settingsStore = useSettingsStore();
 const sideTheme = computed(() => settingsStore.sideTheme);
 </script>

+ 245 - 233
src/views/parameter/basicPublicTemplate/nutritionEducationTemplate/index.vue

@@ -1,21 +1,26 @@
 <template>
     <div class="p-2">
-        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+            :leave-active-class="proxy?.animate.searchAnimate.leave">
             <div v-show="showSearch" class="mb-[10px]">
                 <el-card shadow="hover">
                     <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
                         <el-form-item label="开始时间">
-                            <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+                            <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="-"
+                                start-placeholder="开始日期" end-placeholder="结束日期" />
 
                         </el-form-item>
                         <el-form-item label="创建人科室">
-                            <el-tree-select v-model="queryParams.createDept" :data="treeData" :props="treeProps" placeholder="请选择" check-strictly node-key="id" @keyup.enter="handleQuery" />
+                            <el-tree-select v-model="queryParams.createDept" :data="treeData" :props="treeProps"
+                                placeholder="请选择" check-strictly node-key="id" @keyup.enter="handleQuery" />
                         </el-form-item>
                         <el-form-item label="适用科室">
-                            <el-tree-select v-model="queryParams.deptId" :data="treeData" :props="treeProps" placeholder="请选择" check-strictly node-key="id" @keyup.enter="handleQuery" />
+                            <el-tree-select v-model="queryParams.deptId" :data="treeData" :props="treeProps"
+                                placeholder="请选择" check-strictly node-key="id" @keyup.enter="handleQuery" />
                         </el-form-item>
                         <el-form-item prop="searchValue">
-                            <el-input v-model="queryParams.searchValue" placeholder="模板名称/模板描述/创建人/最后编辑人" clearable style="width:300px" @keyup.enter="handleQuery" />
+                            <el-input v-model="queryParams.searchValue" placeholder="模板名称/模板描述/创建人/最后编辑人" clearable
+                                style="width:300px" @keyup.enter="handleQuery" />
                         </el-form-item>
                         <el-form-item>
                             <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
@@ -30,17 +35,20 @@
             <template #header>
                 <el-row :gutter="10" class="mb8">
                     <el-col :span="1.5">
-                        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['basicPublicTemplate:educationTemplate:add']">新增模板</el-button>
+                        <el-button type="primary" plain icon="Plus" @click="handleAdd"
+                            v-hasPermi="['basicPublicTemplate:educationTemplate:add']">新增模板</el-button>
                     </el-col>
                     <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
                 </el-row>
             </template>
-            <el-table v-loading="loading" border :data="educationTemplateList" @selection-change="handleSelectionChange">
+            <el-table v-loading="loading" border :data="educationTemplateList"
+                @selection-change="handleSelectionChange">
                 <el-table-column type="selection" width="55" align="center" />
                 <el-table-column label="模板名称" align="center" prop="templateName" />
                 <el-table-column label="模板描述" align="center" prop="description">
                     <template #default="scope">
-                        <div class="description-cell" @mouseenter="showTooltip($event, scope.row.description)" @mouseleave="hideTooltip">
+                        <div class="description-cell" @mouseenter="showTooltip($event, scope.row.description)"
+                            @mouseleave="hideTooltip">
                             {{ scope.row.description }}
                         </div>
                     </template>
@@ -55,7 +63,8 @@
                 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                     <template #default="scope">
                         <el-tooltip content="修改" placement="top">
-                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['basicPublicTemplate:educationTemplate:edit']">编辑</el-button>
+                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+                                v-hasPermi="['basicPublicTemplate:educationTemplate:edit']">编辑</el-button>
                         </el-tooltip>
                         <el-tooltip content="详情" placement="top">
                             <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
@@ -64,244 +73,247 @@
                 </el-table-column>
             </el-table>
 
-            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
         </el-card>
+
+        <Teleport to="body">
+            <Transition name="fade">
+                <div v-if="tooltipVisible" class="custom-tooltip" :style="tooltipStyle">
+                    {{ tooltipContent }}
+                </div>
+            </Transition>
+        </Teleport>
     </div>
-    <Teleport to="body">
-        <Transition name="fade">
-            <div v-if="tooltipVisible" class="custom-tooltip" :style="tooltipStyle">
-                {{ tooltipContent }}
-            </div>
-        </Transition>
-    </Teleport>
+
 </template>
 
 <script setup name="EducationTemplate" lang="ts">
-    import { listEducationTemplate, getEducationTemplate, delEducationTemplate } from '@/api/parameter/basicPublicTemplate/nutritionEducationTemplate';
-    import { EducationTemplateVO, EducationTemplateQuery } from '@/api/parameter/basicPublicTemplate/nutritionEducationTemplate/types';
-    import { listDept } from '@/api/system/dept';
-    import { useRouter } from 'vue-router';
-    import type { DeptQuery } from '@/api/system/dept/types';
-
-    const router = useRouter();
-    const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-
-    const educationTemplateList = ref < EducationTemplateVO[] > ([]);
-    const loading = ref(true);
-    const showSearch = ref(true);
-    const ids = ref < Array < string | number >> ([]);
-    const single = ref(true);
-    const multiple = ref(true);
-    const total = ref(0);
-    const treeData = ref([]);
-    const treeProps = ref({
-        value: 'deptId',
-        label: 'deptName',
-        children: 'children'
-    });
-
-    const queryFormRef = ref < ElFormInstance > ();
-
-    const data = reactive({
-        queryParams: {
-            pageNum: 1,
-            pageSize: 10,
-            searchValue: undefined,
-            deptId: undefined,
-            dateRange: undefined,
-            createDept: undefined,
-            params: {}
-        } as EducationTemplateQuery
-    });
-
-    const initQueryParams = () => {
-        queryParams.value = {
-            pageNum: 1,
-            pageSize: 10,
-            searchValue: undefined,
-            deptId: undefined,
-            dateRange: undefined,
-            createDept: undefined,
-            params: {}
-        };
+import { listEducationTemplate, getEducationTemplate, delEducationTemplate } from '@/api/parameter/basicPublicTemplate/nutritionEducationTemplate';
+import { EducationTemplateVO, EducationTemplateQuery } from '@/api/parameter/basicPublicTemplate/nutritionEducationTemplate/types';
+import { listDept } from '@/api/system/dept';
+import { useRouter } from 'vue-router';
+import type { DeptQuery } from '@/api/system/dept/types';
+
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const educationTemplateList = ref<EducationTemplateVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const treeData = ref([]);
+const treeProps = ref({
+    value: 'deptId',
+    label: 'deptName',
+    children: 'children'
+});
+
+const queryFormRef = ref<ElFormInstance>();
+
+const data = reactive({
+    queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        searchValue: undefined,
+        deptId: undefined,
+        dateRange: undefined,
+        createDept: undefined,
+        params: {}
+    } as EducationTemplateQuery
+});
+
+const initQueryParams = () => {
+    queryParams.value = {
+        pageNum: 1,
+        pageSize: 10,
+        searchValue: undefined,
+        deptId: undefined,
+        dateRange: undefined,
+        createDept: undefined,
+        params: {}
     };
-
-    const { queryParams } = toRefs(data);
-
-    // 添加tooltip相关的状态和方法
-    const tooltipVisible = ref(false);
-    const tooltipContent = ref('');
-    const tooltipStyle = ref({
-        left: '0px',
-        top: '0px'
-    });
-
-    const showTooltip = (event: MouseEvent, content: string) => {
-        const target = event.target as HTMLElement;
-        const rect = target.getBoundingClientRect();
-
-        tooltipContent.value = content;
-        tooltipStyle.value = {
-            left: `${rect.left + window.scrollX}px`,
-            top: `${rect.bottom + window.scrollY + 5}px`
-        };
-        tooltipVisible.value = true;
-    };
-
-    const hideTooltip = () => {
-        tooltipVisible.value = false;
+};
+
+const { queryParams } = toRefs(data);
+
+// 添加tooltip相关的状态和方法
+const tooltipVisible = ref(false);
+const tooltipContent = ref('');
+const tooltipStyle = ref({
+    left: '0px',
+    top: '0px'
+});
+
+const showTooltip = (event: MouseEvent, content: string) => {
+    const target = event.target as HTMLElement;
+    const rect = target.getBoundingClientRect();
+
+    tooltipContent.value = content;
+    tooltipStyle.value = {
+        left: `${rect.left + window.scrollX}px`,
+        top: `${rect.bottom + window.scrollY + 5}px`
     };
-
-    /** 查询营养宣教模板列表 */
-    const getList = async () => {
-        loading.value = true;
-        const res = await listEducationTemplate(queryParams.value);
-        educationTemplateList.value = res.rows;
-
-        // 获取部门数据
-        const deptMap = new Map();
-        treeData.value.forEach(dept => {
-            deptMap.set(dept.deptId, dept.deptName);
-            if (dept.children) {
-                dept.children.forEach(child => {
-                    deptMap.set(child.deptId, child.deptName);
-                });
-            }
-        });
-        total.value = res.total;
-        loading.value = false;
-    }
-
-    const getDeptList = async () => {
-        loading.value = true;
-        try {
-            const res = await listDept({
-                    pageNum: 1,
-                    pageSize: 100
-                }as DeptQuery);
-            if (!res.data) {
-                console.warn("部门数据为空");
-                treeData.value = [];
-                return;
-            }
-
-            const processedData = proxy ?.handleTree(res.data, 'deptId');
-            if (!processedData) {
-                console.warn("树形数据处理失败");
-                treeData.value = [];
-                return;
-            }
-
-            treeData.value = processedData;
-        } catch (error) {
-            console.error('获取部门列表失败:', error);
+    tooltipVisible.value = true;
+};
+
+const hideTooltip = () => {
+    tooltipVisible.value = false;
+};
+
+/** 查询营养宣教模板列表 */
+const getList = async () => {
+    loading.value = true;
+    const res = await listEducationTemplate(queryParams.value);
+    educationTemplateList.value = res.rows;
+
+    // 获取部门数据
+    const deptMap = new Map();
+    treeData.value.forEach(dept => {
+        deptMap.set(dept.deptId, dept.deptName);
+        if (dept.children) {
+            dept.children.forEach(child => {
+                deptMap.set(child.deptId, child.deptName);
+            });
+        }
+    });
+    total.value = res.total;
+    loading.value = false;
+}
+
+const getDeptList = async () => {
+    loading.value = true;
+    try {
+        const res = await listDept({
+            pageNum: 1,
+            pageSize: 100
+        } as DeptQuery);
+        if (!res.data) {
+            console.warn("部门数据为空");
             treeData.value = [];
-        } finally {
-            loading.value = false;
+            return;
         }
-    };
 
-    /** 搜索按钮操作 */
-    const handleQuery = () => {
-        queryParams.value.pageNum = 1;
-        getList();
-    }
-
-    /** 重置按钮操作 */
-    const resetQuery = () => {
-        queryFormRef.value ?.resetFields();
-        initQueryParams();
-        handleQuery();
-    }
-
-    /** 多选框选中数据 */
-    const handleSelectionChange = (selection: EducationTemplateVO[]) => {
-        ids.value = selection.map(item => item.id);
-        single.value = selection.length != 1;
-        multiple.value = !selection.length;
-    }
-
-    /** 新增按钮操作 */
-    const handleAdd = () => {
-        router.push('/argManage/nutritionEducationTemplateAdd');
-    }
-
-        /** 查看详情按钮操作 */
-    const handleDetail = (row: EducationTemplateVO) => {
-        router.push({
-            path: '/argManage/nutritionEducationTemplateDetail',
-            query: { id: row.id }
-        });
-    };
-
-    /** 修改按钮操作 */
-    const handleUpdate = (row ? : EducationTemplateVO) => {
-        const id = row ?.id || ids.value[0];
-        router.push({
-            path: '/argManage/nutritionEducationTemplateAdd',
-            query: { id }
-        });
-    }
-
-    /** 删除按钮操作 */
-    const handleDelete = async (row ? : EducationTemplateVO) => {
-        const _ids = row ?.id || ids.value;
-        await proxy ?.$modal.confirm('是否确认删除营养宣教模板编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
-        await delEducationTemplate(_ids);
-        proxy ?.$modal.msgSuccess("删除成功");
-        await getList();
-    }
+        const processedData = proxy?.handleTree(res.data, 'deptId');
+        if (!processedData) {
+            console.warn("树形数据处理失败");
+            treeData.value = [];
+            return;
+        }
 
-    /** 导出按钮操作 */
-    const handleExport = () => {
-        proxy ?.download('system/educationTemplate/export', {
-            ...queryParams.value
-        }, `educationTemplate_${new Date().getTime()}.xlsx`)
+        treeData.value = processedData;
+    } catch (error) {
+        console.error('获取部门列表失败:', error);
+        treeData.value = [];
+    } finally {
+        loading.value = false;
     }
-
-    onMounted(() => {
-        getList();
-        getDeptList();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+    queryFormRef.value?.resetFields();
+    initQueryParams();
+    handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: EducationTemplateVO[]) => {
+    ids.value = selection.map(item => item.id);
+    single.value = selection.length != 1;
+    multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+    router.push('/argManage/nutritionEducationTemplateAdd');
+}
+
+/** 查看详情按钮操作 */
+const handleDetail = (row: EducationTemplateVO) => {
+    router.push({
+        path: '/argManage/nutritionEducationTemplateDetail',
+        query: { id: row.id }
+    });
+};
+
+/** 修改按钮操作 */
+const handleUpdate = (row?: EducationTemplateVO) => {
+    const id = row?.id || ids.value[0];
+    router.push({
+        path: '/argManage/nutritionEducationTemplateAdd',
+        query: { id }
     });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: EducationTemplateVO) => {
+    const _ids = row?.id || ids.value;
+    await proxy?.$modal.confirm('是否确认删除营养宣教模板编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+    await delEducationTemplate(_ids);
+    proxy?.$modal.msgSuccess("删除成功");
+    await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+    proxy?.download('system/educationTemplate/export', {
+        ...queryParams.value
+    }, `educationTemplate_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+    getList();
+    getDeptList();
+});
 </script>
 
 <style scoped>
-    .description-cell {
-        display: -webkit-box;
-        -webkit-box-orient: vertical;
-        -webkit-line-clamp: 2;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        word-break: break-all;
-        line-height: 1.5;
-        max-height: 3em;
-        text-align: left;
-        padding: 0 8px;
-        cursor: pointer;
-    }
-
-    .custom-tooltip {
-        position: fixed;
-        z-index: 9999;
-        background: white;
-        border: 1px solid #e4e7ed;
-        border-radius: 4px;
-        padding: 12px;
-        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-        max-width: 400px;
-        line-height: 1.5;
-        font-size: 14px;
-        word-break: break-all;
-        text-align: left;
-    }
-
-    .fade-enter-active,
-    .fade-leave-active {
-        transition: opacity 0.2s ease;
-    }
-
-    .fade-enter-from,
-    .fade-leave-to {
-        opacity: 0;
-    }
+.description-cell {
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    line-height: 1.5;
+    max-height: 3em;
+    text-align: left;
+    padding: 0 8px;
+    cursor: pointer;
+}
+
+.custom-tooltip {
+    position: fixed;
+    z-index: 9999;
+    background: white;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    padding: 12px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    max-width: 400px;
+    line-height: 1.5;
+    font-size: 14px;
+    word-break: break-all;
+    text-align: left;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+    transition: opacity 0.2s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+    opacity: 0;
+}
 </style>

+ 1 - 0
src/views/patients/enteralNutrition/components/table/MaterialTable.vue

@@ -523,6 +523,7 @@ const openTemplate = () => {
 const handleConfirm = () => {
   dialogVisible.value = false;
   // 这里处理选中模板后的逻辑
+  
 };
 
 onMounted(() => {

+ 278 - 281
src/views/patients/medicalRecord/index.vue

@@ -48,7 +48,8 @@
         </el-menu-item>
       </el-menu>
       <div class="search-bar">
-        <el-input v-model="searchValue" placeholder="姓名/床号/门诊号" size="small" class="search-input" @keyup.enter="handleSearch" />
+        <el-input v-model="searchValue" placeholder="姓名/床号/门诊号" size="small" class="search-input"
+          @keyup.enter="handleSearch" />
         <el-button size="small" type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
       </div>
       <div class="patient-tabs">
@@ -59,16 +60,8 @@
         </el-tabs>
       </div>
       <div class="patient-table">
-        <el-table
-          :data="patientList"
-          border
-          size="small"
-          height="80%"
-          :show-header="true"
-          class="table-patients"
-          :row-class-name="tableRowClassName"
-          @row-click="handleRowClick"
-        >
+        <el-table :data="patientList" border size="small" height="80%" :show-header="true" class="table-patients"
+          :row-class-name="tableRowClassName" @row-click="handleRowClick">
           <el-table-column prop="name" label="姓名" width="60" />
           <el-table-column prop="gender" label="性别" width="50" />
           <el-table-column prop="age" label="年龄" width="80" />
@@ -108,25 +101,27 @@
       <component :is="currentComponent" v-if="currentComponent" :patient-info="patientInfo" @change="handleSelect" />
     </div>
 
-        <!-- 患者详情弹窗 -->
-        <DetailDialog v-model:visible="showDetailDialog" :detail-data="detailData" :physical-activity-dict="physicalActivityDict" @close="handleDetailClose" />
-        <!-- 新增:编辑弹窗 -->
-        <EditDialog v-model:visible="showEditDialog" :edit-data="editData" @close="handleEditClose" @save="handleEditSave" />
-    </div>
+    <!-- 患者详情弹窗 -->
+    <DetailDialog v-model:visible="showDetailDialog" :detail-data="detailData"
+      :physical-activity-dict="physicalActivityDict" @close="handleDetailClose" />
+    <!-- 新增:编辑弹窗 -->
+    <EditDialog v-model:visible="showEditDialog" :edit-data="editData" @close="handleEditClose"
+      @save="handleEditSave" />
+  </div>
 </template>
 
 <script setup name="MedicalRecord" lang="ts">
-    import { listDiagnosis } from '@/api/patients/diagnosis';
-    import { DiagnosisVO, } from '@/api/patients/diagnosis/types';
-    import { ref, onMounted, getCurrentInstance, toRefs, defineAsyncComponent } from 'vue';
-    import { useRoute, useRouter } from 'vue-router';
-    import { ArrowLeft } from '@element-plus/icons-vue';
-    import { listTreatmentUser, getTreatmentUser } from '@/api/workbench/treatmentUser';
-    import DetailDialog from './detailDialog.vue';
-    import EditDialog from './editDialog.vue';
-    const diagnosisList = ref < DiagnosisVO[] > ([]);
-    import { useNutritionDiagnosisStore } from '@/store/modules/diagnosis';
-    const useStore = useNutritionDiagnosisStore();
+import { listDiagnosis } from '@/api/patients/diagnosis';
+import { DiagnosisVO, } from '@/api/patients/diagnosis/types';
+import { ref, onMounted, getCurrentInstance, toRefs, defineAsyncComponent } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ArrowLeft } from '@element-plus/icons-vue';
+import { listTreatmentUser, getTreatmentUser } from '@/api/workbench/treatmentUser';
+import DetailDialog from './detailDialog.vue';
+import EditDialog from './editDialog.vue';
+const diagnosisList = ref<DiagnosisVO[]>([]);
+import { useNutritionDiagnosisStore } from '@/store/modules/diagnosis';
+const useStore = useNutritionDiagnosisStore();
 
 // 动态导入组件
 const MedicalRecord = defineAsyncComponent(() => import('./levelMenu.vue'));
@@ -141,7 +136,7 @@ const NutritionSetting = defineAsyncComponent(() => import('@/views/patients/nut
 const DietTherapy = defineAsyncComponent(() => import('@/views/patients/dietTherapy/index.vue'));
 const ParenteralNutrition = defineAsyncComponent(() => import('@/views/patients/parenteralNutrition/index.vue'));
 const EnteralNutrition = defineAsyncComponent(() => import('@/views/patients/enteralNutrition/index.vue'));
-const EnteralNutritionHistory = defineAsyncComponent(() => import('@/views/patients/enteralNutrition/history.vue'));  
+const EnteralNutritionHistory = defineAsyncComponent(() => import('@/views/patients/enteralNutrition/history.vue'));
 
 const componentMap = {
   medicalRecord: MedicalRecord,
@@ -161,9 +156,9 @@ const componentMap = {
 const currentComponent = ref(componentMap['medicalRecord']);
 const route = useRoute();
 const router = useRouter();
-const {proxy} = getCurrentInstance() as any;
+const { proxy } = getCurrentInstance() as any;
 
-    const activeMenu = ref('medicalRecord');
+const activeMenu = ref('medicalRecord');
 
 const patientInfo = ref({
   id: '',
@@ -179,23 +174,23 @@ const patientInfo = ref({
   bmi: '',
   activity: '',
 
- 
+
 });
 
-    // 弹窗控制
-    const showDetailDialog = ref(false);
-    const detailData = ref({});
-    const physicalActivityDict = ref([]);
+// 弹窗控制
+const showDetailDialog = ref(false);
+const detailData = ref({});
+const physicalActivityDict = ref([]);
 
-    const showEditDialog = ref(false);
-    const editData = ref({} as any);
+const showEditDialog = ref(false);
+const editData = ref({} as any);
 
 const handleSelect = (key: string, other?: string[]) => {
   if (key == 'nutritionScreeningAdd' || key == 'nutritionEvaluationAdd') {
     activeMenu.value = key.replace('Add', '');
-  } if(key=='enteralNutritionHistory'){            
-            activeMenu.value = key.replace('History',''); 
-  }else {
+  } if (key == 'enteralNutritionHistory') {
+    activeMenu.value = key.replace('History', '');
+  } else {
     activeMenu.value = key;
   }
   currentComponent.value = componentMap[key] || null;
@@ -204,156 +199,156 @@ const handleSelect = (key: string, other?: string[]) => {
   patientInfo.value.outpatientNo = route.query.outpatientNo;
 };
 
-    const goBack = () => {
-        router.back();
-    };
+const goBack = () => {
+  router.back();
+};
 
-    const searchValue = ref('');
-    const patientTab = ref('wait');
-    const patientList = ref([]);
-    const loading = ref(false);
-
-    const currentPatientIndex = ref(0);
-    // 添加tooltip相关的状态和方法
-    const tooltipVisible = ref(false);
-    const tooltipContent = ref('');
-    const tooltipStyle = ref({
-        left: '0px',
-        top: '0px'
-    });
-
-    function stripHtml(html) {
-        if (typeof html === 'undefined' || html === null) {
-            return '--';
-        }
-        const div = document.createElement('div');
-        div.innerHTML = html;
-        return div.textContent || div.innerText || '';
+const searchValue = ref('');
+const patientTab = ref('wait');
+const patientList = ref([]);
+const loading = ref(false);
+
+const currentPatientIndex = ref(0);
+// 添加tooltip相关的状态和方法
+const tooltipVisible = ref(false);
+const tooltipContent = ref('');
+const tooltipStyle = ref({
+  left: '0px',
+  top: '0px'
+});
 
-    }
-    const tableRowClassName = ({ row, rowIndex }) => {
-        return rowIndex === currentPatientIndex.value ? 'current-row' : '';
-    };
+function stripHtml(html) {
+  if (typeof html === 'undefined' || html === null) {
+    return '--';
+  }
+  const div = document.createElement('div');
+  div.innerHTML = html;
+  return div.textContent || div.innerText || '';
 
-    const handleRowClick = (row, column, event) => {
-        activeMenu.value = 'medicalRecord';
-        currentComponent.value = componentMap['medicalRecord'];
-        const idx = patientList.value.findIndex((item) => item === row);
-        patientInfo.value = row;
-        patientInfo.value.type = route.query.type;
-        patientInfo.value.outpatientNo = route.query.outpatientNo;
-        if (idx !== -1) {
-            currentPatientIndex.value = idx;
-        }
-        getDiagnosisList();
-    };
+}
+const tableRowClassName = ({ row, rowIndex }) => {
+  return rowIndex === currentPatientIndex.value ? 'current-row' : '';
+};
 
-    const handlePrev = () => {
-        activeMenu.value = 'medicalRecord';
-        currentComponent.value = componentMap['medicalRecord'];
-        if (currentPatientIndex.value > 0) {
-            patientInfo.value = patientList.value[currentPatientIndex.value - 1];
-            patientInfo.value.type = route.query.type;
-            patientInfo.value.outpatientNo = route.query.outpatientNo;
-            currentPatientIndex.value--;
-        }
-        getDiagnosisList();
-    };
+const handleRowClick = (row, column, event) => {
+  activeMenu.value = 'medicalRecord';
+  currentComponent.value = componentMap['medicalRecord'];
+  const idx = patientList.value.findIndex((item) => item === row);
+  patientInfo.value = row;
+  patientInfo.value.type = route.query.type;
+  patientInfo.value.outpatientNo = route.query.outpatientNo;
+  if (idx !== -1) {
+    currentPatientIndex.value = idx;
+  }
+  getDiagnosisList();
+};
 
-    const handleNext = () => {
-        activeMenu.value = 'medicalRecord';
-        currentComponent.value = componentMap['medicalRecord'];
-        patientInfo.value = patientList.value[currentPatientIndex.value + 1];
-        patientInfo.value.type = route.query.type;
-        patientInfo.value.outpatientNo = route.query.outpatientNo;
-        if (currentPatientIndex.value < patientList.value.length - 1) {
-            currentPatientIndex.value++;
-        }
-        getDiagnosisList();
-    };
+const handlePrev = () => {
+  activeMenu.value = 'medicalRecord';
+  currentComponent.value = componentMap['medicalRecord'];
+  if (currentPatientIndex.value > 0) {
+    patientInfo.value = patientList.value[currentPatientIndex.value - 1];
+    patientInfo.value.type = route.query.type;
+    patientInfo.value.outpatientNo = route.query.outpatientNo;
+    currentPatientIndex.value--;
+  }
+  getDiagnosisList();
+};
 
-    const handleTabChange = (tab: string) => {
-        if (tab == 'wait') {
-            getList();
-        } else {
-            patientList.value = [];
-        }
-    };
+const handleNext = () => {
+  activeMenu.value = 'medicalRecord';
+  currentComponent.value = componentMap['medicalRecord'];
+  patientInfo.value = patientList.value[currentPatientIndex.value + 1];
+  patientInfo.value.type = route.query.type;
+  patientInfo.value.outpatientNo = route.query.outpatientNo;
+  if (currentPatientIndex.value < patientList.value.length - 1) {
+    currentPatientIndex.value++;
+  }
+  getDiagnosisList();
+};
 
-    const getDiagnosisList = async () => {
-        const params: any = {
-            pageNum: null,
-            pageSize: null,
-            treatmentUserId: patientInfo.value.id
-        };
-        try {
-            const res = await listDiagnosis(params);
-            diagnosisList.value = res.rows || [];
-            useStore.setDiagnosisList(res.rows || [])
-            if (diagnosisList.value.length > 0) {
-                // 默认选中第一个
-                useStore.setDiagnosisInfo(diagnosisList.value[0])
-            } else {
-                useStore.setDiagnosisInfo({} as DiagnosisVO)
-            }
-        } catch (error) {
-            proxy ?.$modal.msgError('获取列表失败');
-            useStore.setDiagnosisList([])
-        }
+const handleTabChange = (tab: string) => {
+  if (tab == 'wait') {
+    getList();
+  } else {
+    patientList.value = [];
+  }
+};
+
+const getDiagnosisList = async () => {
+  const params: any = {
+    pageNum: null,
+    pageSize: null,
+    treatmentUserId: patientInfo.value.id
+  };
+  try {
+    const res = await listDiagnosis(params);
+    diagnosisList.value = res.rows || [];
+    useStore.setDiagnosisList(res.rows || [])
+    if (diagnosisList.value.length > 0) {
+      // 默认选中第一个
+      useStore.setDiagnosisInfo(diagnosisList.value[0])
+    } else {
+      useStore.setDiagnosisInfo({} as DiagnosisVO)
     }
+  } catch (error) {
+    proxy?.$modal.msgError('获取列表失败');
+    useStore.setDiagnosisList([])
+  }
+}
 
-    // 编辑按钮处理
-    const handleEdit = async () => {
-        if (!patientInfo.value.id) {
-            proxy ?.$modal.msgError('请先选择患者');
-            return;
-        }
-        try {
-            const res = await getTreatmentUser(patientInfo.value.id);
-            editData.value = res.data;
-            showEditDialog.value = true;
-        } catch (error) {
-            console.error('获取患者详情失败:', error);
-            proxy ?.$modal.msgError('获取患者详情失败');
-        }
-    };
+// 编辑按钮处理
+const handleEdit = async () => {
+  if (!patientInfo.value.id) {
+    proxy?.$modal.msgError('请先选择患者');
+    return;
+  }
+  try {
+    const res = await getTreatmentUser(patientInfo.value.id);
+    editData.value = res.data;
+    showEditDialog.value = true;
+  } catch (error) {
+    console.error('获取患者详情失败:', error);
+    proxy?.$modal.msgError('获取患者详情失败');
+  }
+};
 
-    // 详情按钮处理
-    const handleDetail = async () => {
-        if (!patientInfo.value.id) {
-            proxy ?.$modal.msgError('请先选择患者');
-            return;
-        }
-        try {
-            const res = await getTreatmentUser(patientInfo.value.id);
-            detailData.value = res.data;
-            showDetailDialog.value = true;
-        } catch (error) {
-            console.error('获取患者详情失败:', error);
-            proxy ?.$modal.msgError('获取患者详情失败');
-        }
-    };
+// 详情按钮处理
+const handleDetail = async () => {
+  if (!patientInfo.value.id) {
+    proxy?.$modal.msgError('请先选择患者');
+    return;
+  }
+  try {
+    const res = await getTreatmentUser(patientInfo.value.id);
+    detailData.value = res.data;
+    showDetailDialog.value = true;
+  } catch (error) {
+    console.error('获取患者详情失败:', error);
+    proxy?.$modal.msgError('获取患者详情失败');
+  }
+};
 
-    // 详情弹窗关闭处理
-    const handleDetailClose = () => {
-        showDetailDialog.value = false;
-        detailData.value = {};
-    };
+// 详情弹窗关闭处理
+const handleDetailClose = () => {
+  showDetailDialog.value = false;
+  detailData.value = {};
+};
 
-    const handleEditClose = () => {
-        showEditDialog.value = false;
-        editData.value = {};
-    };
+const handleEditClose = () => {
+  showEditDialog.value = false;
+  editData.value = {};
+};
 
-    const handleEditSave = (data) => {
-        showEditDialog.value = false;
-        proxy ?.$modal.msgSuccess('保存成功');
-        getList();
-    };
+const handleEditSave = (data) => {
+  showEditDialog.value = false;
+  proxy?.$modal.msgSuccess('保存成功');
+  getList();
+};
 
-    const waitingCount = ref(0);
-    const treatingCount = ref(0);
-    const treatedCount = ref(0);
+const waitingCount = ref(0);
+const treatingCount = ref(0);
+const treatedCount = ref(0);
 
 const getList = async () => {
   loading.value = true;
@@ -411,9 +406,9 @@ const getList = async () => {
   }
 };
 
-    const handleSearch = () => {
-        getList();
-    };
+const handleSearch = () => {
+  getList();
+};
 
 onMounted(() => {
   // 获取体力活动字典
@@ -426,79 +421,79 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-    .app-container {
-        display: flex;
-        height: calc(100vh - 50px);
-        background-color: #f5f7fa;
-        margin: -10px;
-    }
+.app-container {
+  display: flex;
+  height: calc(100vh - 50px);
+  background-color: #f5f7fa;
+  margin: -10px;
+}
 
-    .left-menu {
-        width: 220px;
-        background-color: #fff;
-        border-right: 1px solid #e6e6e6;
-        display: flex;
-        flex-direction: column;
-        padding-bottom: 60px;
-        /* 增加底部padding,为按钮留出空间 */
-        position: relative;
-        overflow: hidden;
-        /* 防止内容溢出 */
-    }
+.left-menu {
+  width: 220px;
+  background-color: #fff;
+  border-right: 1px solid #e6e6e6;
+  display: flex;
+  flex-direction: column;
+  padding-bottom: 60px;
+  /* 增加底部padding,为按钮留出空间 */
+  position: relative;
+  overflow: hidden;
+  /* 防止内容溢出 */
+}
 
-    .back-button {
-        padding: 16px;
-        display: flex;
-        align-items: center;
-        gap: 8px;
-        cursor: pointer;
-        color: #409eff;
-        border-bottom: 1px solid #e6e6e6;
-        font-size: 14px;
-
-        .el-icon {
-            font-size: 16px;
-        }
+.back-button {
+  padding: 16px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+  color: #409eff;
+  border-bottom: 1px solid #e6e6e6;
+  font-size: 14px;
+
+  .el-icon {
+    font-size: 16px;
+  }
 
-        &:hover {
-            background-color: #f5f7fa;
-        }
-    }
+  &:hover {
+    background-color: #f5f7fa;
+  }
+}
 
-    .medical-menu {
-        border-right: none;
+.medical-menu {
+  border-right: none;
 
-        :deep(.el-menu-item) {
-            font-size: 14px;
-            height: 40px;
-            line-height: 40px;
-        }
-    }
+  :deep(.el-menu-item) {
+    font-size: 14px;
+    height: 40px;
+    line-height: 40px;
+  }
+}
 
-    .search-bar {
-        display: flex;
-        align-items: center;
-        padding: 8px 12px 0 12px;
-        gap: 6px;
-    }
+.search-bar {
+  display: flex;
+  align-items: center;
+  padding: 8px 12px 0 12px;
+  gap: 6px;
+}
 
-    .search-input {
-        flex: 1;
-    }
+.search-input {
+  flex: 1;
+}
 
-    .search-btn {
-        margin-left: 0;
-    }
+.search-btn {
+  margin-left: 0;
+}
 
-    .patient-tabs {
-        padding: 0 12px;
-        margin-top: 8px;
-    }
+.patient-tabs {
+  padding: 0 12px;
+  margin-top: 8px;
+}
 
-    .patient-table {
-        min-width: 220px;
-        overflow-x: auto;
-    }
+.patient-table {
+  min-width: 220px;
+  overflow-x: auto;
+}
 
 .table-patients {
   table-layout: fixed;
@@ -523,46 +518,48 @@ onMounted(() => {
   }
 }
 
-    .nav-btns {
-        display: flex;
-        justify-content: space-between;
-        gap: 8px;
-        position: absolute;
-        left: 0;
-        right: 0;
-        bottom: 12px;
-        padding: 0 12px;
-        background: #fff;
-        z-index: 1;
-        width: calc(100% - 24px);
-        margin: 0 auto;
-    }
+.nav-btns {
+  display: flex;
+  justify-content: space-between;
+  gap: 8px;
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 12px;
+  padding: 0 12px;
+  background: #fff;
+  z-index: 1;
+  width: calc(100% - 24px);
+  margin: 0 auto;
+}
 
-    .main-content {
-        flex: 1;
-        padding: 20px;
-        overflow-y: auto;
-    }
+.main-content {
+  flex: 1;
+  padding: 20px;
+  overflow-y: auto;
+}
 
-    .patient-left-info {
-        background-color: #fff;
-        height: 100px;
-        padding: 10px;
-        border-radius: 4px;
-        margin-bottom: 20px;
-    }
+.patient-left-info {
+  background-color: #fff;
+  height: 100px;
+  padding: 10px;
+  width: 20%;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
 
-    .patient-right-info {
-        background-color: #fff;
-        height: 100px;
-        padding: 20px;
-        border-radius: 4px;
-        margin-bottom: 20px;
-    }
+.patient-right-info {
+  background-color: #fff;
+  height: 100px;
+  width: 80%;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
 
-    .patient-right {
-        background: #fff;
-    }
+.patient-right {
+  background: #fff;
+}
 
 .info-row {
   display: flex;
@@ -591,7 +588,7 @@ onMounted(() => {
   color: #ff4949;
 }
 
-    .gender-icon.male {
-        color: #409eff;
-    }
+.gender-icon.male {
+  color: #409eff;
+}
 </style>