Skip to content
Return to top
On this page

# FatLogicTree 逻辑树 (试验) #

逻辑树

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

基本概念：

- 节点：树形结构中的一个节点。 对应到逻辑语句中， 比如 `1`、 `1+1`、 `a + 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>

## API #