فهرست منبع

填报管理 优化

Hwf 2 ماه پیش
والد
کامیت
ea74f39d5b

+ 2 - 4
src/api/informationissue/informationissue.ts

@@ -59,15 +59,13 @@ export function submitExamine(data) {
 }
 
 // 通讯录
-export function getPhoneList(params) {
+export function getPhoneList() {
   return request({
     url: '/api/info_publish/addressbook/alldepts',
-    method: 'get',
-    params: params
+    method: 'get'
   });
 }
 
-
 // 用户名称列表
 export function getUser(params) {
   return request({

+ 4 - 4
src/types/components.d.ts

@@ -27,7 +27,6 @@ declare module 'vue' {
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
@@ -43,6 +42,7 @@ declare module 'vue' {
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
@@ -52,7 +52,6 @@ declare module 'vue' {
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
-    ElSegmented: typeof import('element-plus/es')['ElSegmented']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
@@ -60,10 +59,9 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
-    ElTimeline: typeof import('element-plus/es')['ElTimeline']
-    ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
+    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     ExcelEditor: typeof import('./../components/ExcelEditor/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
@@ -75,6 +73,8 @@ declare module 'vue' {
     HikvisionPlayer: typeof import('./../components/HKVideo/hikvision-player.vue')['default']
     HKVideo: typeof import('./../components/HKVideo/index.vue')['default']
     IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
+    IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
+    IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']

+ 13 - 34
src/views/dataFilling/fillingAdd.vue

@@ -54,7 +54,7 @@
             </el-col>
             <!-- 选择填报人 -->
             <el-col :span="16">
-              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold;">
+              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold">
                 <div>
                   <el-tag
                     v-for="tag in selectedReporter"
@@ -82,11 +82,7 @@
       <hot-table v-if="showTable" ref="wrapper" :data="hotData" :settings="hotSettings" />
     </div>
   </div>
-  <informantSelect
-    v-model="isShowSelect"
-    :tree-data="treeData"
-    :default-check-data="selectContactSelectData"
-    @confirm="handleContactSelectData"  />
+  <informantSelect v-model="isShowSelect" :tree-data="treeData" :default-check-data="form.user_ids" @confirm="handleContactSelectData" />
 </template>
 
 <script setup lang="ts">
@@ -95,15 +91,13 @@ import { ElButton, ElCol, ElDatePicker, ElFormItem, ElInput, ElOption, ElRow, El
 import * as XLSX from 'xlsx';
 import { fillingAdd } from '@/api/dataFilling/fillingManage';
 import { HotTable } from '@handsontable/vue3';
-import Handsontable from 'handsontable';
 import 'handsontable/languages';
 import 'handsontable/dist/handsontable.full.css';
 import { registerAllModules } from 'handsontable/registry';
-import informantSelect from './informantSelect.vue'
+import informantSelect from './informantSelect.vue';
 import { getPhoneList } from '@/api/informationissue/informationissue';
-import { deepClone } from '@/utils';
-import { createWarehousingEntry } from '@/api/comprehensiveGuarantee/materialReserveManagement/InboundManagement';
 import { validatePhone } from '@/utils/validate';
+import { deepClone } from '@/utils';
 registerAllModules();
 
 const fileList = ref([]);
@@ -117,19 +111,8 @@ const detailData = ref({
 });
 const hotData = ref([]);
 const field_names = ref([]);
-const editableHeaders = ref([]);
-const creator_name = ref('');
-const creator_phone = ref('');
+
 const selectedReporter = ref(null);
-const selectedName =ref();
-const reporters = ref([]); // 确保这个数组被正确初始化
-const selectedTime = ref(null);
-const table_name = ref('');
-const data_table_name = ref(''); // 假设这是另一个输入字段,需要在模板中添加对应的输入框
-const status = ref(0); // 假设这是一个选择器或输入框
-const issued_status = ref(2); // 假设这是一个选择器或输入框
-const period_type = ref(''); // 假设这是一个输入框或选择器
-const creator_id = ref(null); // 这通常是用户ID,可能需要从登录信息中获取
 const isShowSelect = ref(false);
 let treeData = ref([]);
 
@@ -244,7 +227,6 @@ const handleSave = async (formEl,statuCode) => {
       return false;
     }
   });
-
 };
 
 const handleReturn = () => {
@@ -367,7 +349,7 @@ const showSelect = () => {
 
 const fetchTreeData = async () => {
   try {
-    const response = await getPhoneList({});
+    const response = await getPhoneList();
     if (response && response.data) {
       treeData.value = response.data;
     }
@@ -376,22 +358,19 @@ const fetchTreeData = async () => {
   }
 };
 
-const selectContactSelectData = ref([]);
 const handleContactSelectData = (data) => {
-  console.log('handleContactSelectData:', data);
-  selectContactSelectData.value = data;
-  // data.user_ids = data;
-  selectedReporter.value = data;
+  selectedReporter.value = deepClone(data);
   const data1 = [];
-  selectContactSelectData.value.forEach((item) => {
+  data.forEach((item) => {
     data1.push(item.id);
-  })
+  });
   form.value.user_ids = data1;
 };
 
-const handleClose = (tag: string) => {
-  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1)
-}
+const handleClose = (tag: any) => {
+  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1);
+  form.value.user_ids.splice(form.value.user_ids.indexOf(tag.id), 1);
+};
 </script>
 
 <style scoped>

+ 69 - 50
src/views/dataFilling/fillingIssued.vue

@@ -29,13 +29,13 @@
         <el-button type="primary" @click="handleSaveTemporarily(1)">暂存</el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button type="primary" @click="handleSave(formRef,2)"> 发布 </el-button>
+        <el-button type="primary" @click="handleSave(formRef, 2)"> 发布 </el-button>
       </el-col>
       <el-col :span="1.5">
         <el-button type="danger" @click="handleReturn()"> 返回 </el-button>
       </el-col>
     </el-row>
-    <el-form :model="form" :rules="rules" ref="formRef">
+    <el-form ref="formRef" :model="form" :rules="rules">
       <el-row :gutter="20">
         <el-col :lg="30" :xs="24" style="margin-top: -10px">
           <el-row :span="24" :gutter="10">
@@ -54,7 +54,15 @@
             <!-- 截止时间 -->
             <el-col :span="8">
               <el-form-item label="截&nbsp止&nbsp时&nbsp间&nbsp:" prop="end_time">
-                <el-date-picker v-model="form.end_time" value-format="YYYY-MM-DD HH:mm:ss" time-format="HH:mm" type="datetime" placeholder="选择截止时间" style="width: 300px" @change="handleTimeChange" />
+                <el-date-picker
+                  v-model="form.end_time"
+                  value-format="YYYY-MM-DD HH:mm:ss"
+                  time-format="HH:mm"
+                  type="datetime"
+                  placeholder="选择截止时间"
+                  style="width: 300px"
+                  @change="handleTimeChange"
+                />
               </el-form-item>
             </el-col>
             <!-- 操作按钮 -->
@@ -65,15 +73,15 @@
             </el-col>
             <!-- 选择填报人 -->
             <el-col :span="16">
-              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold;">
+              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold">
                 <div>
                   <el-tag
                     v-for="tag in selectedReporter"
                     :key="tag"
                     closable
                     :disable-transitions="false"
-                    @close="handleClose(tag)"
                     style="margin-right: 10px"
+                    @close="handleClose(tag)"
                   >
                     {{ tag.label }}
                   </el-tag>
@@ -95,25 +103,23 @@
   </div>
   <informantSelect
     v-model="isShowSelect"
+    v-isShowSelect
     :tree-data="treeData"
-    :default-check-data="selectContactSelectData"
-    @confirm="handleContactSelectData"  />
+    :default-check-data="form.user_ids"
+    @confirm="handleContactSelectData"
+  />
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
-import { ElButton, ElCol, ElDatePicker, ElFormItem, ElInput, ElOption, ElRow, ElSelect, ElTable, ElTableColumn } from 'element-plus';
 import * as XLSX from 'xlsx';
 import { fillingAdd, fillingList } from '@/api/dataFilling/fillingManage';
 import { HotTable } from '@handsontable/vue3';
-import Handsontable from 'handsontable';
 import 'handsontable/languages';
 import 'handsontable/dist/handsontable.full.css';
 import { registerAllModules } from 'handsontable/registry';
-import informantSelect from './informantSelect.vue'
+import informantSelect from './informantSelect.vue';
 import { getPhoneList } from '@/api/informationissue/informationissue';
 import { deepClone } from '@/utils';
-import { createWarehousingEntry } from '@/api/comprehensiveGuarantee/materialReserveManagement/InboundManagement';
 import { validatePhone } from '@/utils/validate';
 registerAllModules();
 
@@ -122,7 +128,7 @@ const props = defineProps<{
 }>();
 const fileList = ref([]);
 const { proxy } = getCurrentInstance();
-const emits = defineEmits(['close','confirm']);
+const emits = defineEmits(['close', 'confirm']);
 const formRef = ref('');
 const detailData = ref({
   title: '表单数据',
@@ -136,9 +142,9 @@ const isShowSelect = ref(false);
 let treeData = ref([]);
 
 // 初始化表格数据
-onMounted(() => {
-  fetchTreeData();
-  fetchData();
+onMounted(async () => {
+  await fetchData();
+  await fetchTreeData();
 });
 
 const showTable = ref(false);
@@ -148,16 +154,16 @@ const handleNewTemplate = () => {
 };
 
 const form = ref({
-  table_name: "",
-  end_time: "",
-  status: "0",
-  issued_status: "",
-  period_type: "",
-  creator_name: "",
-  creator_phone: "",
+  table_name: '',
+  end_time: '',
+  status: '0',
+  issued_status: '',
+  period_type: '',
+  creator_name: '',
+  creator_phone: '',
   field_names: [],
   user_ids: []
-})
+});
 const rules = {
   table_name: [{ required: true, message: '表名不能为空', trigger: 'blur' }],
   end_time: [{ required: true, message: '截止时间不能为空', trigger: 'blur' }],
@@ -166,8 +172,8 @@ const rules = {
     { required: true, message: '联系电话不能为空', trigger: 'blur' },
     { validator: validatePhone, message: '请输入正确格式的联系电话', trigger: 'blur' }
   ],
-  user_ids: [{ required: true, message: '请选择填报人', trigger: 'blur' }],
-}
+  user_ids: [{ required: true, message: '请选择填报人', trigger: 'blur' }]
+};
 const handleSaveTemporarily = async (statuCode) => {
   if (hotData.value && hotData.value[0] && hotData.value[0].length > 0) {
     const data2 = [];
@@ -175,7 +181,7 @@ const handleSaveTemporarily = async (statuCode) => {
       if (!!item) {
         data2.push(item);
       }
-    })
+    });
     form.value.field_names = data2;
   }
   form.value.issued_status = statuCode;
@@ -185,7 +191,7 @@ const handleSaveTemporarily = async (statuCode) => {
   });
 };
 
-const handleSave = async (formEl,statuCode) => {
+const handleSave = async (formEl, statuCode) => {
   if (!formEl) return;
   await formEl.validate((valid, fields) => {
     if (valid) {
@@ -194,7 +200,7 @@ const handleSave = async (formEl,statuCode) => {
         if (!!item) {
           data2.push(item);
         }
-      })
+      });
       form.value.field_names = data2;
       form.value.issued_status = statuCode;
       fillingAdd(form.value).then((res) => {
@@ -216,7 +222,6 @@ const handleSave = async (formEl,statuCode) => {
       return false;
     }
   });
-
 };
 
 const handleReturn = () => {
@@ -335,44 +340,58 @@ const handleFileUpload = ({ file }) => {
 
 const showSelect = () => {
   isShowSelect.value = true;
-}
+};
 
-const fetchTreeData = async () => {
-  try {
-    const response = await getPhoneList({});
-    if (response && response.data) {
-      treeData.value = response.data;
+const fetchTreeData = () => {
+  return getPhoneList().then((response) => {
+    treeData.value = response.data;
+    if (form.value && form.value.user_ids) {
+      let arr = [];
+      form.value.user_ids.forEach((item) => {
+        let res = findNodeById(treeData.value, item);
+        if (!!res) {
+          arr.push(res);
+        }
+      });
+      selectedReporter.value = arr;
     }
-  } catch (error) {
-    console.error('Failed to fetch information:', error);
-  }
+  });
 };
 
-const selectContactSelectData = ref([]);
 const handleContactSelectData = (data) => {
-  console.log('handleContactSelectData:', data);
-  selectContactSelectData.value = data;
-  // data.user_ids = data;
-  selectedReporter.value = data;
+  selectedReporter.value = deepClone(data);
   const data1 = [];
-  selectContactSelectData.value.forEach((item) => {
+  data.forEach((item) => {
     data1.push(item.id);
-  })
+  });
   form.value.user_ids = data1;
 };
 
 const handleClose = (tag: string) => {
-  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1)
-}
+  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1);
+  form.value.user_ids.splice(form.value.user_ids.indexOf(tag.id), 1);
+};
 
 const fetchData = () => {
-  fillingList(props.id).then((res) => {
+  return fillingList(props.id).then((res) => {
     form.value.creator_name = res.report_info.creator_name;
     form.value.creator_phone = res.report_info.creator_phone;
     form.value.end_time = res.report_info.end_time;
     form.value.table_name = res.report_info.table_name;
     form.value.user_ids = res.report_info.user_ids;
-  })
+  });
+};
+function findNodeById(data, targetId) {
+  for (const node of data) {
+    if (node.id === targetId) {
+      return { id: node.id, label: node.label }; // 找到匹配节点,直接返回
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findNodeById(node.children, targetId); // 递归搜索子节点
+      if (found) return { id: found.id, label: found.label }; // 子节点中找到则返回
+    }
+  }
+  return null; // 未找到返回 null
 }
 </script>
 

+ 8 - 16
src/views/dataFilling/informantSelect.vue

@@ -1,10 +1,11 @@
 <template>
-  <el-dialog v-model="visible" title="选择消息接收人" width="780px" append-to-body @close="closeDialog">
+  <el-dialog v-model="visible" title="选择消息接收人" width="780px" :destroy-on-close="false" @close="closeDialog">
     <div class="container">
       <div class="left-content">
         <div>人员选择列表</div>
         <el-input v-model="filterText" placeholder="输入关键字进行搜索" class="input" />
         <el-tree
+          v-if="visible"
           ref="treeRef"
           class="filter-tree"
           :data="treeData"
@@ -12,7 +13,7 @@
           :props="defaultProps"
           :filter-node-method="filterNode"
           :default-checked-keys="defaultCheckData"
-          node-key="uuid"
+          node-key="id"
           @check-change="handleCheckChange"
         >
           <template #default="{ node, data }">
@@ -48,7 +49,7 @@
   </el-dialog>
 </template>
 
-<script setup name="Contact">
+<script setup name="InformantSelect">
 import { deepClone } from '@/utils/index';
 import userImg from '@/assets/images/user.png';
 import fileImg from '@/assets/images/file.png';
@@ -69,23 +70,15 @@ let checkData = ref([]);
 watch(filterText, (val) => {
   treeRef.value.filter(val);
 });
-const initData = () => {
-  if (!!props.defaultCheckData) {
-    props.defaultCheckData.forEach((item) => {
-      treeRef.value.setChecked(item.uuid, true, true);
-    });
-    checkData.value = deepClone(props.defaultCheckData);
-  }
-};
 watch(
   () => props.modelValue,
   () => {
     visible.value = props.modelValue;
     nextTick(() => {
       if (!!props.modelValue) {
-        initData();
+        handleCheckChange();
       }
-    })
+    });
   },
   {
     immediate: true
@@ -106,7 +99,7 @@ const getImage = (item) => {
 };
 const handleCheckChange = () => {
   const data = [];
-  const treeData = treeRef.value.getCheckedNodes(false, false);
+  const treeData = treeRef.value.getCheckedNodes();
   treeData.forEach((item) => {
     if (!item.deptType) {
       if (!data.some((obj) => obj.id === item.id)) {
@@ -126,9 +119,8 @@ const uncheckNodeById = (nodeId, index) => {
   });
 };
 const closeDialog = () => {
-  emits('update:modelValue', false);
-  treeRef.value.setCheckedKeys([], false);
   checkData.value = [];
+  emits('update:modelValue', false);
 };
 const confirm = () => {
   const data = deepClone(checkData.value);

+ 46 - 43
src/views/dataFilling/tableDetails.vue

@@ -52,10 +52,7 @@
           </el-col>
           <el-col :span="8">
             <el-form-item label="" prop="people_name" label-width="auto">
-<!--              <el-select v-model="selectedReporter" placeholder="请选择填报人">-->
-<!--                <el-option v-for="reporter in reporters" :key="reporter.id" :label="reporter.name" :value="reporter.id" />-->
-<!--              </el-select>-->
-              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold;">
+              <el-form-item label="选择填报人:" prop="user_ids" label-width="auto" style="font-weight: bold">
                 <div>
                   <el-tag
                     v-for="tag in selectedReporter"
@@ -72,7 +69,6 @@
               </el-form-item>
             </el-form-item>
           </el-col>
-
         </el-row>
       </el-col>
     </el-row>
@@ -81,22 +77,17 @@
     <hot-table v-if="tableData && tableData.length > 0" ref="wrapper" :data="tableData" :settings="hotSettings" />
     <hot-table v-if="isShowTable" ref="wrapper" :data="hotData" :settings="hotSettings" />
   </div>
-  <informantSelect
-    v-model="isShowSelect"
-    :tree-data="treeData"
-    :default-check-data="reportInfo.user_ids"
-    @confirm="handleContactSelectData"  />
+  <informantSelect v-model="isShowSelect" :tree-data="treeData" :default-check-data="reportInfo.user_ids" @confirm="handleContactSelectData" />
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch } from 'vue';
-import { ElButton, ElCol, ElFormItem, ElInput, ElOption, ElRow, ElSelect, ElTable, ElTableColumn } from 'element-plus';
 import * as XLSX from 'xlsx';
 import { fillingList } from '@/api/dataFilling/fillingManage';
-import { useRoute, useRouter } from 'vue-router';
+import { useRouter } from 'vue-router';
 import { HotTable } from '@handsontable/vue3';
 import { getPhoneList } from '@/api/informationissue/informationissue';
-import informantSelect from './informantSelect.vue'
+import informantSelect from './informantSelect.vue';
+import { deepClone } from '@/utils';
 
 const isShowSelect = ref(false);
 let treeData = ref([]);
@@ -119,12 +110,11 @@ const reportInfo = ref({
   updated_at: '',
   num_reported: 0,
   num_unreported: 0,
-  user_ids:[]
+  user_ids: []
 });
 const is_filling_ended = ref(0);
 const tableData = ref([]);
 const selectedReporter = ref(null);
-const route = useRoute();
 const router = useRouter();
 
 const props = defineProps({
@@ -140,49 +130,49 @@ watch(reportId, async (newVal) => {
 
 // 初始化表格数据
 onMounted(async () => {
-  fetchTreeData();
   created();
   if (reportId.value) {
     await fetchReportDetails(reportId.value);
   }
+  await fetchTreeData();
 });
 
 const fetchReportDetails = (reportId) => {
-  fillingList(reportId).then(res => {
-    res.report_info.user_ids = [7, 8,11,4,1,12];
-    let user1 = [];
-    res.report_info.user_ids.forEach((user) => {
-      user1.push({uuid: user});
-    })
+  return fillingList(reportId).then((res: any) => {
+    res.report_info.user_ids = [7, 8, 11, 4, 1, 12];
     reportInfo.value = res.report_info;
-    reportInfo.value.user_ids = user1;
-    tableData.value = res.table_data
-    debugger
-  })
-}
+    tableData.value = res.table_data;
+  });
+};
 
-const handleClose = (tag: string) => {
-  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1)
-}
+const handleClose = (tag: any) => {
+  selectedReporter.value.splice(selectedReporter.value.indexOf(tag), 1);
+  reportInfo.value.user_ids.splice(reportInfo.value.user_ids.indexOf(tag.id), 1);
+};
 const showSelect = () => {
   isShowSelect.value = true;
-}
-const fetchTreeData = async () => {
-  const response = await getPhoneList({});
-  if (response && response.data) {
+};
+const fetchTreeData = () => {
+  return getPhoneList().then((response) => {
     treeData.value = response.data;
-  }
+    if (reportInfo.value && reportInfo.value.user_ids) {
+      let arr = [];
+      reportInfo.value.user_ids.forEach((item) => {
+        let res = findNodeById(treeData.value, item);
+        if (!!res) {
+          arr.push(res);
+        }
+      });
+      selectedReporter.value = arr;
+    }
+  });
 };
-const selectContactSelectData = ref([]);
 const handleContactSelectData = (data) => {
-  console.log('handleContactSelectData:', data);
-  selectContactSelectData.value = data;
-  // data.user_ids = data;
-  selectedReporter.value = data;
+  selectedReporter.value = deepClone(data);
   const data1 = [];
-  selectContactSelectData.value.forEach((item) => {
+  data.forEach((item) => {
     data1.push(item.id);
-  })
+  });
   reportInfo.value.user_ids = data1;
 };
 
@@ -344,4 +334,17 @@ const handleData = () => {
   // 跳转到数据档案管理-详情页
   router.push({ name: 'DataArchiveDetail', params: { reportId: reportInfo.value.report_id } });
 };
+
+function findNodeById(data, targetId) {
+  for (const node of data) {
+    if (node.id === targetId) {
+      return { id: node.id, label: node.label }; // 找到匹配节点,直接返回
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findNodeById(node.children, targetId); // 递归搜索子节点
+      if (found) return { id: found.id, label: found.label }; // 子节点中找到则返回
+    }
+  }
+  return null; // 未找到返回 null
+}
 </script>