Browse Source

调整树形表格和表单模板,参考实际实现的代码优化模板结构

- 更新树形表格模板,移除展开/折叠功能,简化操作
- 优化树形表单模板,添加TypeScript类型定义和更好的错误处理
- 改进数据获取和表单验证逻辑
- 统一API调用方式和参数传递
PIG AI 1 week ago
parent
commit
8b80c48494
3 changed files with 138 additions and 147 deletions
  1. 1 48
      tree/树形api.ts
  2. 131 71
      tree/树形表单.vue
  3. 6 28
      tree/树形表格.vue

+ 1 - 48
tree/树形api.ts

@@ -126,51 +126,4 @@ export function validateExist(rule: any, value: any, callback: any, isEdit: bool
       callback();
     }
   });
-}
-
-/**
- * 构建前端树形结构数据
- * @param data - 平铺数据数组
- * @param parentId - 父级ID,默认为0或null
- * @returns 树形结构数据
- */
-export function buildTreeData(data: any[], parentId: string | number | null = null): any[] {
-  const result: any[] = [];
-  
-  data.forEach(item => {
-    if (item.parentId === parentId || (parentId === null && (item.parentId === null || item.parentId === 0 || item.parentId === '0'))) {
-      const children = buildTreeData(data, item.${pk.attrName});
-      const node = {
-        ...item,
-        children: children.length > 0 ? children : undefined,
-        hasChildren: children.length > 0
-      };
-      result.push(node);
-    }
-  });
-  
-  return result;
-}
-
-/**
- * 扁平化树形数据
- * @param treeData - 树形数据
- * @returns 扁平化后的数据数组
- */
-export function flattenTreeData(treeData: any[]): any[] {
-  const result: any[] = [];
-  
-  function traverse(nodes: any[]) {
-    nodes.forEach(node => {
-      const { children, ...nodeData } = node;
-      result.push(nodeData);
-      
-      if (children && children.length > 0) {
-        traverse(children);
-      }
-    });
-  }
-  
-  traverse(treeData);
-  return result;
-} 
+}

+ 131 - 71
tree/树形表单.vue

@@ -123,33 +123,75 @@
 </template>
 
 <script setup lang="ts" name="${ClassName}TreeDialog">
-// ========== 1. 导入语句 ==========
+// ========== 导入语句 ==========
+import { useMessage } from "/@/hooks/message";
+import { getObj, addObj, putObj, getParentNodes } from '/@/api/${moduleName}/${functionName}';
+#if($fieldDict && $fieldDict.size() > 0)
 import { useDict } from '/@/hooks/dict';
+#end
+#foreach($field in $formList)
+#if($field.formValidator && $field.formValidator != 'duplicate')
 import { rule } from '/@/utils/validate';
-import { useMessage } from "/@/hooks/message";
-import { getObj, addObj, putObj, validateExist, getParentNodes } from '/@/api/${moduleName}/${functionName}';
+#break
+#end
+#end
+
+// ========== 类型定义 ==========
+interface TreeNode {
+  ${pk.attrName}: string | number | null;
+#foreach($field in $formList)
+#if($field.attrName == 'name')
+  name: string;
+#break
+#end
+#end
+  children?: TreeNode[];
+}
 
-// ========== 2. 组件定义 ==========
-// 定义组件事件
+interface FormData {
+#if(!$formList.contains(${pk.attrName}))
+  ${pk.attrName}?: string;
+#end
+  parentId?: string | number | null;
+#foreach($field in $formList)
+#if($field.attrName != 'parentId')
+#if($field.formType == 'number')
+  ${field.attrName}: number;
+#elseif($field.formType == 'checkbox')
+  ${field.attrName}: any[];
+#else
+  ${field.attrName}: string;
+#end
+#end
+#end
+}
+
+// ========== 组件定义 ==========
 const emit = defineEmits(['refresh']);
 
-// ========== 3. 响应式数据定义 ==========
-// 基础响应式变量
+// ========== 响应式数据定义 ==========
 const dataFormRef = ref(); // 表单引用
 const visible = ref(false); // 弹窗显示状态
 const loading = ref(false); // 加载状态
-const parentNodes = ref([]); // 父级节点数据
+const parentNodes = ref<TreeNode[]>([]); // 父级节点数据
 
 // 树形选择器配置
 const treeSelectProps = {
   children: 'children',
-  label: 'name', // 假设显示字段为name,请根据实际情况调整
+#foreach($field in $formList)
+#if($field.attrName == 'name')
+  label: 'name',
+#break
+#else
+  label: '${field.attrName}', // 请根据实际显示字段调整
+#end
+#end
   value: '${pk.attrName}',
   checkStrictly: true
 };
 
 // 表单数据对象
-const form = reactive({
+const form = reactive<FormData>({
 #if(!$formList.contains(${pk.attrName}))
   ${pk.attrName}: '', // 主键
 #end
@@ -167,9 +209,9 @@ const form = reactive({
 #end
 });
 
-// ========== 4. 字典数据处理 ==========
+// ========== 字典数据处理 ==========
 #set($fieldDict=[])
-#foreach($field in $gridList)
+#foreach($field in $formList)
 #if($field.fieldDict)
 #set($void=$fieldDict.add($field.fieldDict))
 #end
@@ -179,8 +221,21 @@ const form = reactive({
 const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict));
 #end
 
-// ========== 5. 表单校验规则 ==========
+// ========== 表单校验规则 ==========
 const dataRules = ref({
+  parentId: [
+    { 
+      required: true, 
+      validator: (rule: any, value: any, callback: any) => {
+        if (value === null || value === undefined || value === '') {
+          callback(new Error('请选择父级节点'));
+        } else {
+          callback();
+        }
+      },
+      trigger: 'change' 
+    }
+  ],
 #foreach($field in $formList)
 #if($field.formRequired == '1' && $field.formValidator == 'duplicate')
   ${field.attrName}: [
@@ -210,14 +265,17 @@ const dataRules = ref({
 #end
 });
 
-// ========== 6. 方法定义 ==========
-// 获取详情数据
+// ========== 方法定义 ==========
+/**
+ * 获取详情数据
+ */
 const get${ClassName}Data = async (id: string) => {
   try {
     loading.value = true;
     const { data } = await getObj({ ${pk.attrName}: id });
-    // 直接将第一条数据赋值给表单
-    Object.assign(form, data[0]);
+    if (data && data.length > 0) {
+      Object.assign(form, data[0]);
+    }
   } catch (error) {
     useMessage().error('获取数据失败');
   } finally {
@@ -225,14 +283,16 @@ const get${ClassName}Data = async (id: string) => {
   }
 };
 
-// 获取父级节点数据
+/**
+ * 获取父级节点数据
+ */
 const getParentNodesList = async () => {
   try {
     const { data } = await getParentNodes();
     // 添加根节点选项
     parentNodes.value = [
       { ${pk.attrName}: null, name: '根节点', children: [] },
-      ...buildParentTree(data)
+      ...(data || [])
     ];
   } catch (error) {
     console.error('获取父级节点失败:', error);
@@ -240,90 +300,90 @@ const getParentNodesList = async () => {
   }
 };
 
-// 构建父级节点树形结构
-const buildParentTree = (data: any[]) => {
-  // 如果后端已经返回树形结构,直接返回
-  if (data && data.length > 0 && data[0].children !== undefined) {
-    return data;
-  }
-  
-  // 如果是平铺数据,需要构建树形结构
-  const map = new Map();
-  const roots: any[] = [];
-  
-  data.forEach(item => {
-    map.set(item.${pk.attrName}, { ...item, children: [] });
-  });
+/**
+ * 打开弹窗方法
+ * @param id 编辑时的数据ID
+ * @param parentId 新增时的父级ID
+ */
+const openDialog = (id?: string, parentId?: string | number) => {
+  visible.value = true;
   
-  data.forEach(item => {
-    const node = map.get(item.${pk.attrName});
-    if (item.parentId) {
-      const parent = map.get(item.parentId);
-      if (parent) {
-        parent.children.push(node);
-      } else {
-        roots.push(node);
-      }
-    } else {
-      roots.push(node);
-    }
+  // 重置表单数据
+  Object.assign(form, {
+#if(!$formList.contains(${pk.attrName}))
+    ${pk.attrName}: '',
+#end
+    parentId: parentId || null, // 新增时设置父级ID
+#foreach($field in $formList)
+#if($field.attrName != 'parentId')
+#if($field.formType == 'number')
+    ${field.attrName}: 0,
+#elseif($field.formType == 'checkbox')
+    ${field.attrName}: [],
+#else
+    ${field.attrName}: '',
+#end
+#end
+#end
   });
-  
-  return roots;
-};
-
-// 打开弹窗方法
-const openDialog = (id: string, parentId?: string) => {
-  visible.value = true;
-  form.${pk.attrName} = '';
-  form.parentId = parentId || null;
 
   // 获取父级节点数据
   getParentNodesList();
 
-  // 重置表单数据
+  // 重置表单验证
   nextTick(() => {
     dataFormRef.value?.resetFields();
   });
 
-  // 获取${ClassName}信息
+  // 如果是编辑模式,获取数据详情
   if (id) {
     form.${pk.attrName} = id;
     get${ClassName}Data(id);
   }
 };
 
-// 提交表单方法
+/**
+ * 提交表单方法
+ */
 const onSubmit = async () => {
-  loading.value = true; // 防止重复提交
+  // 防止重复提交
+  if (loading.value) return;
   
   // 表单校验
-  const valid = await dataFormRef.value.validate().catch(() => {});
-  if (!valid) {
-    loading.value = false;
-    return false;
+  try {
+    await dataFormRef.value.validate();
+  } catch {
+    return;
   }
 
+  loading.value = true;
+  
   try {
-    // 处理父级ID为空的情况
-    if (!form.parentId) {
-      form.parentId = null;
-    }
+    // 处理父级ID,如果选择根节点(null)则设为null,其他情况保持原值
+    const submitData = {
+      ...form,
+      parentId: form.parentId === null ? null : form.parentId
+    };
     
     // 根据是否有ID判断是新增还是修改
-    form.${pk.attrName} ? await putObj(form) : await addObj(form);
-    useMessage().success(form.${pk.attrName} ? '修改成功' : '添加成功');
+    if (form.${pk.attrName}) {
+      await putObj(submitData);
+      useMessage().success('修改成功');
+    } else {
+      await addObj(submitData);
+      useMessage().success('添加成功');
+    }
+    
     visible.value = false;
     emit('refresh'); // 通知父组件刷新列表
   } catch (err: any) {
-    useMessage().error(err.msg);
+    useMessage().error(err.msg || '操作失败');
   } finally {
     loading.value = false;
   }
 };
 
-// ========== 7. 对外暴露 ==========
-// 暴露方法给父组件
+// ========== 对外暴露 ==========
 defineExpose({
   openDialog
 });

+ 6 - 28
tree/树形表格.vue

@@ -84,19 +84,11 @@
             icon="folder-add" 
             type="primary" 
             class="ml10" 
-            @click="formDialogRef.openDialog()"
+            @click="formDialogRef.openDialog(undefined, null)"
             v-auth="'${moduleName}_${functionName}_add'"
           >
             新增
           </el-button>
-          <el-button 
-            icon="sort" 
-            type="primary" 
-            class="ml10" 
-            @click="expandAll"
-          >
-            展开/折叠
-          </el-button>
 
           <el-button 
             plain 
@@ -127,7 +119,6 @@
         :cell-style="tableStyle.cellStyle" 
         :header-cell-style="tableStyle.headerCellStyle"
         @selection-change="selectionChangHandle"
-        :default-expand-all="isExpandAll"
       >
         <el-table-column type="selection" width="40" align="center" />
         <el-table-column type="index" label="#" width="40" />
@@ -135,7 +126,7 @@
 #if($field.fieldDict)
         <el-table-column prop="${field.attrName}" label="#if(${field.fieldComment})${field.fieldComment}#else${field.attrName}#end" show-overflow-tooltip>
           <template #default="scope">
-            <dict-tag :options="$field.fieldDict" :value="scope.row.${field.attrName}" />
+            <dict-tag :options="${field.fieldDict}" :value="scope.row.${field.attrName}" />
           </template>
         </el-table-column>
 #else
@@ -156,7 +147,7 @@
               text 
               type="primary" 
               v-auth="'${moduleName}_${functionName}_add'"
-              @click="formDialogRef.openDialog('', scope.row.${pk.attrName})"
+              @click="formDialogRef.openDialog(undefined, scope.row.${pk.attrName})"
             >
               新增
             </el-button>
@@ -185,8 +176,6 @@
 
     <!-- 编辑、新增弹窗 -->
     <form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
-
-
   </div>
 </template>
 
@@ -213,7 +202,7 @@ const FormDialog = defineAsyncComponent(() => import('./form.vue'));
 #set($void=$fieldDict.add($field.fieldDict))
 #end
 #end
-#if($fieldDict)
+#if($fieldDict && $fieldDict.size() > 0)
 // 加载字典数据
 const { $dict.format($fieldDict) } = useDict($dict.quotation($fieldDict));
 #end
@@ -226,7 +215,6 @@ const queryRef = ref();               // 查询表单引用
 const showSearch = ref(true);         // 是否显示搜索区域
 const selectObjs = ref([]) as any;    // 表格多选数据
 const multiple = ref(true);           // 是否多选
-const isExpandAll = ref(false);       // 是否展开所有节点
 
 // ========== 表格状态 ==========
 const state: BasicTableProps = reactive<BasicTableProps>({
@@ -237,7 +225,6 @@ const state: BasicTableProps = reactive<BasicTableProps>({
   dataList: []        // 数据列表
 });
 
-
 // ========== Hook引用 ==========
 // 表格相关Hook (树形表格不使用分页)
 const {
@@ -259,20 +246,11 @@ const resetQuery = () => {
 };
 
 /**
- * 展开/折叠所有节点
- */
-const expandAll = () => {
-  isExpandAll.value = !isExpandAll.value;
-};
-
-
-
-/**
  * 表格多选事件处理
  * @param objs 选中的数据行
  */
-const selectionChangHandle = (objs: { $pk.attrName: string }[]) => {
-  selectObjs.value = objs.map(({ $pk.attrName }) => $pk.attrName);
+const selectionChangHandle = (objs: { ${pk.attrName}: string }[]) => {
+  selectObjs.value = objs.map(({ ${pk.attrName} }) => ${pk.attrName});
   multiple.value = !objs.length;
 };