Skip to content
On this page

FatLogicTree 逻辑树 (试验)

逻辑树


逻辑树是一个树形容器,可以用来表示一些逻辑关系。

基本概念:

  • 节点:树形结构中的一个节点。 对应到逻辑语句中, 比如 11+1a + 1 > 2,这些表达式都属于节点
  • 分组:将多个节点组合起来,约束一些逻辑关系。 比如 (a > 1) && (b < 0) 就是一个分组,分组包含 (a > 1)(b < 0) 两个节点
  • 逻辑关系: 当前仅支持 AND、OR 两种关系。当然这个概念是可以互换的,比如 并行互斥

因此逻辑树和普通数在结构上是有一些约束的:

  • 节点只能放在分组中
  • 节点不能作为分组的上级
  • 分组可以包含分组



示例

自定义树结构

FatLogicTree 本身并不耦合约束树的结构,可以声明树的结构、逻辑的标识等等

查看代码
vue
<template>
  <FatLogicTree v-model="data" :tree-struct="customTreeStruct" and-text="互斥" or-text="并行">
    <!-- 自定义节点渲染 -->
    <template #node="scope">
      <div class="node">
        <div>VALUE: {{ scope.current.value }}</div>
        <div>INDEX: {{ scope.index }}</div>
        <div>PATH: {{ scope.path }}</div>
        <div>DEPTH: {{ scope.depth }}</div>
      </div>
    </template>
  </FatLogicTree>
</template>

<script setup lang="tsx">
  import { ref } from 'vue';
  import { FatLogicTree, LogicType, FatLogicTreeStruct } from '@wakeadmin/components';

  type Group = {
    uuid: string | number;
    category: 'group';
    logic?: '并行' | '互斥';
    // eslint-disable-next-line no-use-before-define
    list: Item[];
  };

  type Node = {
    category: 'node';
    uuid: string | number;
    value: string;
  };
  type Item = Group | Node;

  // 自定义树结构
  const customTreeStruct: FatLogicTreeStruct<Item> = {
    // 子节点获取
    children: 'list',
    // 唯一标识符获取
    id: 'uuid',
    // 判断是否为逻辑分组
    isGroup: i => i.category === 'group',
    // 获取逻辑类型
    getLogicType: i => ((i as Group).logic === '并行' ? LogicType.OR : LogicType.AND),
    // 写入逻辑类型
    setLogicType: (i, type) => {
      if (i.category === 'group') {
        return {
          ...i,
          logic: type === LogicType.OR ? '并行' : '互斥',
        };
      }

      return i;
    },
  };

  const data = ref<Item>({
    uuid: 'root',
    category: 'group',
    logic: '互斥',
    list: [
      {
        uuid: 1,
        category: 'group',
        logic: '并行',
        list: [
          {
            uuid: 31,
            category: 'node',
            value: 'Item 1',
          },
          {
            uuid: 32,
            category: 'node',
            value: 'Item 2',
          },
        ],
      },
      {
        uuid: 2,
        category: 'group',
        logic: '并行',
        list: [
          {
            uuid: 31,
            category: 'node',
            value: 'Item 1',
          },
          {
            uuid: 32,
            category: 'node',
            value: 'Item 2',
          },
        ],
      },
    ],
  });
</script>

<style scoped>
  .node {
    background-color: white;
    padding: 4px;
    border-radius: 4px;
    margin-bottom: 8px;
    border-radius: 4px;
  }
</style>

节点操作



查看代码
tsx
import { defineComponent, ref } from 'vue';
import { FatLogicTree, LogicType } from '@wakeadmin/components';
import { ElButton } from 'element-plus';
import s from './LogicTreeOperation.module.scss';

interface Group {
  id: string | number;
  category: 'group';
  type?: LogicType;
  children: Item[];
}
interface Node {
  category: 'node';
  id: string | number;
  value: string;
}

type Item = Group | Node;

export default defineComponent({
  setup() {
    const data = ref<Item>({
      category: 'group',
      id: 'root',
      type: LogicType.OR,
      children: [],
    });

    return () => {
      // 支持泛型
      return (
        <div>
          <FatLogicTree<Item>
            modelValue={data.value}
            onUpdate:modelValue={v => (data.value = v)}
            // 自定义分组渲染
            // 用于复杂的样式定义
            renderGroup={scope => {
              const current = scope.current as Group;

              if (scope.depth === 0) {
                // 根节点
                return (
                  <div class={s.root}>
                    {current.children.length === 0 && <div class={s.empty}>这里啥都没有</div>}
                    {scope.vdom}
                    <ElButton
                      class={s.button}
                      onClick={() => {
                        scope.append({
                          category: 'group',
                          id: 'group-' + current.children.length,
                          type: LogicType.AND,
                          children: [
                            {
                              category: 'node',
                              id: 'node',
                              value: 'Example',
                            },
                          ],
                        });
                      }}
                    >
                      添加分组
                    </ElButton>
                  </div>
                );
              }

              return (
                <div class={s.group}>
                  <h3>分组: {scope.index + 1}</h3>
                  <div class={s.subGroup}>{scope.vdom}</div>
                </div>
              );
            }}
            // 自定义节点渲染
            renderNode={scope => {
              const current = scope.current as Node;

              // 插入子节点
              const handleAddChild = () => {
                const id = Date.now();
                scope.insertAfter({
                  category: 'node',
                  id,
                  value: `Example ${id}`,
                });
              };

              return (
                <div class={s.node}>
                  {current.value}-{scope.path} {scope.index === 0 && <ElButton onClick={handleAddChild}>添加</ElButton>}
                  <ElButton onClick={scope.remove}>删除</ElButton>
                </div>
              );
            }}
          ></FatLogicTree>
          <pre>
            <code>{JSON.stringify(data.value, undefined, 2)}</code>
          </pre>
        </div>
      );
    };
  },
});
scss
.root {
  border: 1px solid rgb(180, 180, 180);
  padding: 10px;
  border-radius: 10px;
}

.group {
  background-color: rgb(135, 208, 135);
  border-radius: 4px;
  padding: 5px;

  margin-bottom: 10px;
}

.subGroup {
  margin-top: 1em;
}

.node {
  background-color: white;
  padding: 4px;
  border-radius: 4px;
  margin-bottom: 4px;
}

.button {
  margin-top: 5px;
}



和 FatForm 配合使用

查看代码
tsx
import { defineComponent, ref } from 'vue';
import { defineFatForm, FatFormItem, FatLogicTree, LogicType } from '@wakeadmin/components';
import { ElButton } from 'element-plus';
import s from './LogicTreeOperation.module.scss';

interface Group {
  id: string | number;
  category: 'group';
  type?: LogicType;
  children: Item[];
}
interface Node {
  category: 'node';
  id: string | number;
  value: string;
}

type Item = Group | Node;

export default defineFatForm(({ group, consumer }) => {
  return () => ({
    submit: async value => {
      console.log('保存', value);
    },
    onValidateFailed: errors => {
      console.log('验证失败', errors);
    },
    children: [
      group({
        label: '逻辑分组',
        prop: 'logic',
        initialValue: {
          category: 'group',
          id: 'root',
          type: LogicType.OR,
          children: [],
        },
        rules: {
          validator(rule, value, callback) {
            if (value == null || !(value as Group).children?.length) {
              callback(new Error('请至少添加一个分组'));
            } else {
              callback();
            }
          },
        },
        children: [
          consumer(formScope => {
            return (
              <FatLogicTree<Item>
                basePath="logic"
                modelValue={formScope.getFieldValue('logic')}
                onUpdate:modelValue={v => formScope.setFieldValue('logic', v)}
                // 自定义分组渲染
                // 用于复杂的样式定义
                renderGroup={scope => {
                  const current = scope.current as Group;

                  if (scope.depth === 0) {
                    // 根节点
                    return (
                      <div class={s.root}>
                        {current.children.length === 0 && <div class={s.empty}>这里啥都没有</div>}
                        {scope.vdom}
                        <ElButton
                          class={s.button}
                          onClick={() => {
                            scope.append({
                              category: 'group',
                              id: 'group-' + current.children.length,
                              type: LogicType.AND,
                              children: [
                                {
                                  category: 'node',
                                  id: 'node',
                                  value: 'Example',
                                },
                              ],
                            });
                          }}
                        >
                          添加分组
                        </ElButton>
                      </div>
                    );
                  }

                  return (
                    <div class={s.group}>
                      <h3>分组: {scope.index + 1}</h3>
                      <div class={s.subGroup}>{scope.vdom}</div>
                    </div>
                  );
                }}
                // 自定义节点渲染
                renderNode={scope => {
                  const current = scope.current as Node;

                  // 插入子节点
                  const handleAddChild = () => {
                    const id = Date.now();
                    scope.insertAfter({
                      category: 'node',
                      id,
                      value: `Example ${id}`,
                    });
                  };

                  return (
                    <div class={s.node}>
                      <FatFormItem
                        prop={scope.path + '.value'}
                        rules={{ required: true, message: '不能为空' }}
                      ></FatFormItem>
                      <div>
                        {scope.index === 0 && <ElButton onClick={handleAddChild}>添加</ElButton>}
                        <ElButton onClick={scope.remove}>删除</ElButton>
                      </div>
                    </div>
                  );
                }}
              ></FatLogicTree>
            );
          }),
        ],
      }),
      group({
        label: '数据',
        children: [
          consumer(scope => {
            return (
              <pre>
                <code>{JSON.stringify(scope.values, undefined, 2)}</code>
              </pre>
            );
          }),
        ],
      }),
    ],
  });
});




多层嵌套



查看代码
vue
<template>
  <FatLogicTree v-model="data">
    <!-- 自定义节点渲染 -->
    <template #node="scope">
      <div class="node">
        <div>VALUE: {{ scope.current.value }}</div>
        <div>INDEX: {{ scope.index }}</div>
        <div>PATH: {{ scope.path }}</div>
        <div>DEPTH: {{ scope.depth }}</div>
      </div>
    </template>
  </FatLogicTree>
</template>

<script setup lang="tsx">
  import { ref } from 'vue';
  import { FatLogicTree, LogicType } from '@wakeadmin/components';

  type Item =
    | {
        id: string | number;
        category: 'group';
        type?: LogicType;
        children: Item[];
      }
    | {
        category: 'node';
        id: string | number;
        value: string;
      };

  const data = ref<Item>({
    id: 'root',
    category: 'group',
    type: LogicType.OR,
    children: [
      {
        category: 'node',
        id: 1,
        value: 'Item 1',
      },
      {
        id: 2,
        category: 'node',
        value: 'Item 2',
      },
      {
        id: 3,
        category: 'group',
        children: [
          {
            id: 31,
            category: 'node',
            value: 'Item 3-1',
          },
          {
            id: 32,
            category: 'node',
            value: 'Item 3-2',
          },
        ],
      },
      {
        id: 4,
        category: 'group',
        children: [
          {
            id: 41,
            value: 'Item 4-1',
            category: 'node',
          },
          {
            id: 42,
            value: 'Item 4-2',
            category: 'node',
          },
        ],
      },
      {
        id: 5,
        category: 'group',
        type: LogicType.OR,
        children: [],
      },
      {
        id: 6,
        type: LogicType.OR,
        category: 'group',
        children: [
          {
            id: 61,
            category: 'group',
            children: [
              {
                id: 611,
                category: 'group',
                children: [
                  {
                    id: 6111,
                    value: 'Item 6-1-1-1',
                    category: 'node',
                  },
                  {
                    id: 6112,
                    category: 'group',
                    type: LogicType.OR,
                    children: [
                      {
                        id: 61121,
                        category: 'group',
                        type: LogicType.OR,
                        children: [
                          {
                            id: 611211,
                            value: 'Item 6-1-1-2-1-1',
                            category: 'node',
                          },
                          {
                            id: 611212,
                            value: 'Item 6-1-1-2-1-2',
                            category: 'node',
                          },
                        ],
                      },
                    ],
                  },
                ],
              },
              {
                id: 612,
                value: 'Item 6-1-2',
                category: 'node',
              },
            ],
          },
          {
            id: 62,
            value: 'Item 6-2',
            category: 'node',
          },
        ],
      },
    ],
  });
</script>

<style scoped>
  .node {
    background-color: white;
    padding: 4px;
    border-radius: 4px;
    margin-bottom: 8px;
    border-radius: 4px;
  }
</style>




可拖拽

配合 sortablejs 实现

API