npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@pluve/lego-tree-vue

v0.1.1

Published

乐高系列之 tree 组件

Downloads

134

Readme

@pluve/lego-tree-vue

乐高系列之 tree 组件系列,属于强业务组件。

npm (scoped)

@pluve/lego-tree-vue 一期二期规划的功能已上线,目前已在【营销工具 - 一元券】等项目中使用。

安装

# 依赖 [email protected]/[email protected]/@ant-design/[email protected]/@pluve/lego-excel-vue@*
yarn add @pluve/lego-tree-vue

公共类型 LegoTreeTypes

LegoTreeTypes.OrgTreeJsonFromEnum

组织机构树 orgTree.json 来源枚举。当走 SSO 方式时,不区分全量 或者 有效 的数据源

export enum OrgTreeJsonFromEnum {
  SSO = 'sso', // SSO(默认,不区分全量 or 有效)
  USER = 'user', // 账号中心
}

LegoTreeTypes.OrgTreeJsonTypeEnum

组织机构树 json 类型:全量(包含已关停等) or 有效的,提供枚举。

export enum OrgTreeJsonTypeEnum {
  ALL = 'all', // 全量的数据
  ENABLE = 'enable', // 有效的数据(默认)
}

LegoTreeTypes.CommonHierarchyEnum

组织机构树原始树结构返回的 hierarchy 字段枚举。

export enum CommonHierarchyEnum {
  COMPANY = '01', // 公司
  CENTER = '02', // 中心
  OPERATING_AREA = '03', // 营运区
  DEPARTMENT = '04', // 部门
  GROUP = '05', // 组
  AREA = '06', // 片区
  STORE = '07', // 门店
}

LegoTreeTypes.IOriginTreeNode

接口返回的原始节点信息,除 [key: string] 外,其他字段都为 通用组织机构树接口 返回的字段。

| 参数 | 说明 | 类型 | 是否可选 | | --- | --- | --- | --- | | code | 节点编码 | string | 是 | | hierarchy | (SSO)组织机构级别:01(公司);02(中心);03(营运区);04(部门);05(组);06(片区);07(门店); | string | 是 | | rank | (账号中心)组织机构级别:01(公司);02(中心);03(营运区);04(部门);05(组);06(片区);07(门店); | LegoTreeTypes.CommonHierarchyEnum | 是 | | id | 唯一 key | string | 否 | | name | 名称 | string | 是 | | parentId | 父 id | string | 是 | | storeCode | 门店编码 | string | 是 | | disabled | 是否节点禁用 | boolean | 是 | | selectable | 【单选】节点是否可以选择 | boolean | 是 | | sub | (SSO)子节点 | LegoTreeTypes.IOriginTreeNode[] | 是 | | children | (账号中心)子节点 | LegoTreeTypes.IOriginTreeNode[] | 是 | | [key: string] | 其他字段,用于匹配非组织机构树接口的返回字段 | any | 是 |

LegoTreeTypes.ITreeNodesType

接口返回数据转化后的节点信息,必须按照如下类型来传输。

| 参数 | 说明 | 类型 | 是否可选 | | --- | --- | --- | --- | | key | 唯一 key。走默认数据源取数逻辑 && 账号中心全量数据时,key 取值方式:code_storeCode;走默认数据源取数逻辑 && SSO 或 账号中心有效数据时,key 取值方式:code/storeCode; | string | 否 | | value | 唯一 keyLegoTreeSelect 组件使用。走默认数据源取数逻辑 && 账号中心全量数据时,value 取值方式:code_storeCode;走默认数据源取数逻辑 && SSO 或 账号中心有效数据时,value 取值方式:code/storeCode; | string | 是 | | title | 节点名称 | string | 是 | | hierarchy | 组织机构级别:01(公司);02(中心);03(营运区);04(部门);05(组);06(片区);07(门店);若接口返回该字段为空,则组件内部处理为从1开始往下叠加 | string | 是 | | code | 原始 code | string | 是 | | storeCode | 原始 storeCode,门店节点时才存在该字段 | string | 是 | | pid | 上一节点的 'code' 字段 | string | 是 | | trace | 父子节点路径,includeAreatrue 时,该字段必须有值 | string | 是 | | disabled | 是否节点禁止操作 | boolean | 是 | | selectable | 【单选】节点是否可以选择 | boolean | 是 | | children | 子节点 | LegoTreeTypes.ITreeNodesType[] | 是 | | originNode | 原始树节点信息,keepOriginNode 开启后 该对象不为 undefined,且在 includeAreatruelabelInValuetrue 时返回给调用方 | LegoTreeTypes.IOriginTreeNode | 是 |

LegoTreeTypes.ICheckedAreaItem

可选择父子层级的选中项节点信息

| 参数 | 说明 | 类型 | 是否可选 | | --- | --- | --- | --- | | key | 唯一 key。走默认数据源取数逻辑 && 账号中心全量数据时,key 取值方式:code_storeCode;走默认数据源取数逻辑 && SSO 或 账号中心有效数据时,key 取值方式:code/storeCode; | string | 否 | | title | 节点名称 | string | 是 | | hierarchy | 组织机构级别:01(公司);02(中心);03(营运区);04(部门);05(组);06(片区);07(门店);若接口返回该字段为空,则组件内部处理为从1开始往下叠加 | string | 是 | | code | 原始 code | string | 是 | | storeCode | 原始 storeCode,门店节点时才存在该字段 | string | 是 | | originNode | 原始树节点信息,keepOriginNode 开启后 该对象不为 undefined,且在 includeAreatrue时返回给调用方 | LegoTreeTypes.IOriginTreeNode | 是 |

LegoTreeTypes.ITreeSelectLabelInValue

LegoTreeSelect 组件 开启 labelInValue 时的 value 类型,即 当前节点的信息 & antd TreeSelect 组件返回的当前节点的信息的并集

export interface ITreeSelectLabelInValue extends Partial<LegoTreeTypes.ITreeNodesType> {
  /**
   * @description 走默认数据源取数逻辑 && 账号中心全量数据时,value 取值方式:code_storeCode
   * 走默认数据源取数逻辑 && SSO 或 账号中心有效数据时,value 取值方式:code/storeCode
   * @author yangwen
   * @type {string}
   * @memberof ITreeSelectLabelInValue
   */
  value: string;
  label?: string;
  disabled?: boolean;
}

| 参数 | 说明 | 类型 | 是否可选 | | --- | --- | --- | --- | | value | 唯一 keyLegoTreeSelect 组件使用。走默认数据源取数逻辑 && 账号中心全量数据时,value 取值方式:code_storeCode;走默认数据源取数逻辑 && SSO 或 账号中心有效数据时,value 取值方式:code/storeCode; | string | 否 | | label | 节点名称,与 title 字段取值一致 | string | 是 | | disabled | 是否节点禁止操作 | boolean | 是 |

树数据源处理

优先级:treeData > request > orgTree.json

1. treeData

若传值 treeData 不为 undefined,则组件认定是调用方需要自定义数据源,则完全信任调用方传入的数据源,组件内部不对 treeData 做处理。 同时接收 loading 字段 和 dataListMap 字段,作为加载数据源的 加载状态扁平化的数据源。 此时需要区分 2 种情况:

  1. dataListMap 为空对象:则调用 LegoTreeUtils.generateListMap 方法来生成 扁平化数据源 dataListMap 并存储在组件内部,同时给 treeData 子节点增加 trace 字段用作记录父子节点的路径。

  2. dataListMap 不为空对象:则默认调用方已经扁平化了数据源 且给子节点增加了 trace 字段,直接存储在组件内部即可。

注意:

  • trace 字段主要用于组织机构树可以选择任意节点的场景,需要通过 trace 字段来判定父子节点间的关系。如果只需要选择子节点,则可以忽略所有针对 trace 字段的要求。
  • treeData 里面每个节点的 originNode 字段需要自行处理includeAreatruelabelInValuetrue 时,若开启 keepOriginNode,则 返回的值里面会增加 originNode 字段。

2. request

若传值 treeDataundefined,且 request 不为 undefined,则组件认定是调用方需要通过自定义服务来自定义数据源,则完全信任调用方传入自定义服务,组件内部执行调用服务逻辑。 获取数据源后,通过调用 LegoTreeUtils.generateListMap 方法来生成 扁平化数据源 dataListMap 并存储在组件内部,同时给 treeData 子节点增加 trace 字段用作记录父子节点的路径。

注意:

  • trace 字段主要用于组织机构树可以选择任意节点的场景,需要通过 trace 字段来判定父子节点间的关系。如果只需要选择子节点,则可以忽略所有针对 trace 字段的要求。
  • treeData 里面每个节点的 originNode 字段需要自行处理includeAreatruelabelInValuetrue 时,若开启 keepOriginNode,则 返回的值里面会增加 originNode 字段。

3. orgTree.json

若上述 2 个字段都为 undefined,则组件认定调用方没有自定义的需求,则直接走组件默认的数据源取数逻辑:

  1. 根据 envType 字段区分走测试还是生产环境的接口调用——注意:此时获取到的数据没有做任何权限处理
  2. 根据 jsonFrom 字段区分走 SSO 还是走账号中心——注意:SSO 只返回有效的数据源,不包含已关停数据,如需已关停数据,请走账号中心
  3. 根据 jsonType 字段区分返回全量(包含已关停等)还是有效的数据源,获取全部的组织机构树的数据源
  4. 根据 hierarchyList 字段过滤不包含的节点信息,传空或不传则默认取全量数据源
  5. 调用 LegoTreeUtils.recursionTreeData 方法对数据源做处理,转化为 Tree/TreeSelect 组件需要的数据源
  6. 根据 selectHierarchy 字段过滤子节点不包含 selectHierarchy 层级的节点。例如,不包含门店节点的父节点全部过滤掉不展示,则 selectHierarchy 为 '07'。
  7. 调用 LegoTreeUtils.generateListMap 方法来生成 扁平化数据源 dataListMap 并存储在组件内部,同时给 treeData 子节点增加 trace 字段用作记录父子节点的路径,并更新 treeData 且展示在树上。

相关源码

const loadOrgTreeNodeByLevel = async () => {
  if (state.loading) {
    return;
  }

  state.loading = true;

  // 此处 使用 toRaw 针对 props.treeData/props.dataListMap(响应式对象) 做转化
  if (Object.prototype.toString.call(props.treeData) === '[object Array]') {
    // 此处需要确保传的 treeData 已经做了数据处理且包含 trace 字段
    state.treeData = toRaw(props.treeData || []);
    // dataListMap 有值则直接取
    if (Object.values(toRaw(props.dataListMap || {})).length) {
      // 此处需要确保传的 dataListMap 已经做了数据处理
      state.dataListMap = toRaw(props.dataListMap || {});
    } else {
      // dataListMap 没有值则计算再赋值
      const { dataList, dataListMap } = LegoTreeUtils.generateListMap(
        toRaw(props.treeData || [])
      );
      // 此时 treeData 增加了 trace 字段
      state.treeData = dataList;
      state.dataListMap = dataListMap;
    }

    state.loading = false;
    // 接口请求成功后,往外触发 getTreeInfo 事件,获取树相关数据
    emit('getTreeInfo', {
      treeData: toRaw(state.treeData),
      dataListMap: toRaw(state.dataListMap),
    });
    return;
  }

  LegoTreeUtils.loadOrgTreeNodeByLevel(
    {
      request: props.request,
      selectHierarchy: props.selectHierarchy,
      envType: props.envType,
      jsonFrom: props.jsonFrom,
      jsonType: props.jsonType,
      hierarchyList: props.hierarchyList,
      keepOriginNode: props.keepOriginNode ?? false,
    },
    (dataList, dataListMap) => {
      state.treeData = dataList;
      state.dataListMap = dataListMap;
      state.loading = false;
      // 接口请求成功后,往外触发 getTreeInfo 事件,获取树相关数据
      emit('getTreeInfo', {
        treeData: dataList,
        dataListMap: dataListMap,
      });
    }
  );
};
const loadOrgTreeNodeByLevel = async (
  {
    request,
    selectHierarchy = '',
    envType = 'test',
    jsonFrom = LegoTreeTypes.OrgTreeJsonFromEnum.SSO,
    jsonType = LegoTreeTypes.OrgTreeJsonTypeEnum.ENABLE,
    hierarchyList = [],
    enableSelectedHierarchy = [],
    keepOriginNode = false,
  }: {
    request?: () => Promise<LegoTreeTypes.ITreeNodesType[]>; // 自定义 request 接口请求
    selectHierarchy?: string; // 选择的组织机构级别:01(公司);02(中心);03(营运区);04(部门);05(组);06(片区);07(门店);
    envType?: 'test' | 'prod'; // 环境(测试 | 生产)
    jsonFrom?: LegoTreeTypes.OrgTreeJsonFromEnum; // 组织机构树 orgTree.json 来源
    jsonType?: LegoTreeTypes.OrgTreeJsonTypeEnum; // 组织机构树 json 类型:全量(包含已关停等) or 有效的
    hierarchyList?: string[]; // 组织机构级别数组,默认全部
    enableSelectedHierarchy?: string[]; // 【单选】可选择的组织机构级别,不传或为空则都可以选择,传了则包含的级别才可以选择
    keepOriginNode?: boolean; // 是否树节点保留原始对象信息
  },
  callback: (
    treeData: LegoTreeTypes.ITreeNodesType[],
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>
  ) => void
) => {
  try {
    let newTreeData: LegoTreeTypes.ITreeNodesType[] = [];
    // 直接传接口进来,则直接调接口,接口中已经对数据做了处理
    if (typeof request !== 'undefined') {
      newTreeData = await request();
    } else {
      // 否则走默认接口,组件中做处理
      const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
        hierarchyList,
        envType,
        jsonFrom,
        jsonType,
      });
      // 将接口返回的数据结构转化为 TreeSelect/Tree 组件需要的数据结构
      newTreeData = LegoTreeUtils.recursionTreeData({
        list:
          result?.[0][jsonFrom === LegoTreeTypes.OrgTreeJsonFromEnum.SSO ? 'sub' : 'children'] ||
          [],
        childrenKey: jsonFrom === LegoTreeTypes.OrgTreeJsonFromEnum.SSO ? 'sub' : 'children',
        hierarchyKey: jsonFrom === LegoTreeTypes.OrgTreeJsonFromEnum.SSO ? 'hierarchy' : 'rank',
        pid: undefined,
        enableSelectedHierarchy, // 【单选】可选择的组织机构级别,不传或为空则都可以选择,传了则包含的级别才可以选择
        keepOriginNode,
        includeDisabledData: LegoTreeUtils.isAllIncludeDisabledData({ jsonFrom, jsonType }),
      });
      // 过滤树底层没有 selectHierarchy 的节点
      LegoTreeUtils.excludeNode(newTreeData, selectHierarchy);
    }

    const { dataList, dataListMap } = LegoTreeUtils.generateListMap(newTreeData);
    callback(dataList, dataListMap);
  } catch (error) {
    callback([], {});
  }
};

LegoTreeSelect

通常用于单选组织架构,例如选择门店、公司、各组织机构等。props 继承自 antd vue TreeSelectProps

export interface ILegoTreeSelectProps
  extends Omit<
    TreeSelectProps,
    | 'defaultValue'
    | 'fieldNames'
    | 'multiple'
    | 'replaceFields'
    | 'searchValue'
    | 'showSearch'
    | 'treeCheckable'
    | 'treeCheckStrictly'
    | 'treeData'
    | 'treeDataSimpleMode'
    | 'virtual'
    | 'onSearch'
    | 'loading'
    | 'value'
    | 'onChange'
  > {
  envType?: 'test' | 'prod';
  jsonFrom?: OrgTreeJsonFromEnum;
  jsonType?: OrgTreeJsonTypeEnum;
  hierarchyList?: string[];
  selectHierarchy?: string;
  enableSelectedHierarchy?: string[];

  treeData?: LegoTreeTypes.ITreeNodesType[];
  loading?: boolean;
  dataListMap?: Record<string, LegoTreeTypes.ITreeNodesType>;
  request?: () => Promise<LegoTreeTypes.ITreeNodesType[]>;

  defaultValue?: string | LegoTreeTypes.ITreeSelectLabelInValue;
  value?: string | LegoTreeTypes.ITreeSelectLabelInValue;

  keepOriginNode?: boolean;
}

API

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | envType | 环境(测试 | 生产)。走组件默认接口请求时使用,其余场景无需传该字段 | test \| prod | 是 | test | | jsonFrom | 组织机构树 json 来源。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonFromEnum | 是 | LegoTreeTypes.OrgTreeJsonFromEnum.SSO | | jsonType | 组织机构树 json 类型:全量(包含已关停等) or 有效的。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonTypeEnum | 是 | LegoTreeTypes.OrgTreeJsonTypeEnum.ENABLE | | hierarchyList | 组织机构级别数组,01-07,用于过滤不包含的节点信息,没传或为空则不过滤。走组件默认接口请求时使用,其余场景无需传该字段 | string[] | 是 | - | | selectHierarchy | 选择的组织机构级别,01-07,用于过滤子节点不包含 selectHierarchy 层级的节点。走组件默认接口请求时使用,其余场景无需传该字段 | string | 是 | - | | enableSelectedHierarchy | 【单选】可选择的组织机构级别,不传或为空则都可以选择,传了则包含的级别才可以选择,否则 selectablefalse 不可选择 | string[] | 是 | [] | | treeData | antd vue TreeSelect 组件数据源(已经经过转化后的数据源) | LegoTreeTypes.ITreeNodesType[] | 是 | - | | loading | 外部数据源加载时的 loading 状态 | boolean | 是 | false | | dataListMap | 扁平化 Map(已经经过转化后的数据源生成的 map) | Record<string, LegoTreeTypes.ITreeNodesType> | 是 | {} | | request | 自定义服务,用于获取组件树数据源 | () => Promise<LegoTreeTypes.ITreeNodesType[]> | 是 | - | | defaultValue | 默认选中的值 | string \| LegoTreeTypes.ITreeSelectLabelInValue | 是 | - | | value(v-model) | 当前选中的值 | string \| LegoTreeTypes.ITreeSelectLabelInValue | 是 | - | | keepOriginNode | 是否树节点保留原始对象信息。若开启,配合 labelInValue 字段为 true,则会返回 originNode(原始对象信息) | boolean | 是 | false | | title | 自定义标题,在原始基础上,增加当前搜索值 searchValue 字段,供自定义判断处理 | slot({ key, title, ..., searchValue }) | 是 | - |

事件(emit)

| 事件名称 | 说明 | 回调参数 | | --- | --- | --- | | change | 选中树节点时调用此函数 | function(string \| LegoTreeTypes.ITreeSelectLabelInValue) | | getTreeInfo | 接口请求完成后,调用此函数,返回当前组件内部的树数据源 | function({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

ref

通过 ref 获取组件提供的方法或属性。以下是提供的方法或属性: | 方法/属性名称 | 说明 | 类型 | | --- | --- | --- | | getTreeInfo | 【方法】提供组件内部的树数据源 | () => ({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

效果展示

测试环境地址:https://yf-test-oss.yifengx.com/webtest/yangwen/legoTree/index.html#/treeSelect

使用示例

<template>
  <ConfigProvider :locale="zh_CN">
    <div :class="$style.wrap">
      <Form :colon="false" autocomplete="off">
        <Row :gutter="20">
          <!-- 有效数据源 -->
          <Col :span="12">
            <FormItem label="labelInValue true" v-bind="form.validateInfos.rangeCodeObj">
              <LegoTreeSelect
                :hierarchyList="['01', '02', '03', '04', '06', '07']"
                selectHierarchy="07"
                :enable-selected-hierarchy="['01', '07']"
                v-model:value="modelRef.rangeCodeObj"
                :labelInValue="true"
                :default-value="{ value: 'H535' }"
                :keep-origin-node="true"
                ref="treeRef"
                @get-tree-info="getTreeInfo"
              >
                <template #title="{ value: val, title, label, storeCode }">
                  <b v-if="val === '5024'" style="color: #08c">{{ title || label }}</b>
                  <b v-else-if="storeCode === '5500'" style="color: #f90">{{ title || label }}</b>
                  <template v-else>{{ title || label }}</template>
                </template>
              </LegoTreeSelect>
            </FormItem>
          </Col>
          <Col :span="12">
            <FormItem label="labelInValue false" v-bind="form.validateInfos.rangeCode">
              <LegoTreeSelect
                :treeData="state.treeData"
                :loading="state.loading"
                v-model:value="modelRef.rangeCode"
                :labelInValue="false"
              ></LegoTreeSelect>
            </FormItem>
          </Col>

          <!-- 账号中心 && 全部数据源(包含已关停) -->
          <Col :span="24" style="font-weight: bold; color: red; margin-bottom: 24px">
            以下数据源为账号中心全量数据源(包含已关停数据):
          </Col>
          <Col :span="12">
            <FormItem label="labelInValue true" v-bind="form.validateInfos.rangeCodeObjAll">
              <LegoTreeSelect
                :json-from="LegoTreeTypes.OrgTreeJsonFromEnum.USER"
                :json-type="LegoTreeTypes.OrgTreeJsonTypeEnum.ALL"
                :hierarchyList="['01', '02', '03', '04', '06', '07']"
                selectHierarchy="07"
                :enable-selected-hierarchy="['01', '07']"
                v-model:value="modelRef.rangeCodeObjAll"
                :labelInValue="true"
                :default-value="{ value: '10025961_H535' }"
                :keep-origin-node="true"
                ref="treeRefAll"
                @get-tree-info="getTreeInfoAll"
              >
                <template #title="{ value: val, title, label, storeCode }">
                  <b v-if="val === '10004575_5024'" style="color: #08c">{{ title || label }}</b>
                  <b v-else-if="storeCode === '5500'" style="color: #f90">{{ title || label }}</b>
                  <template v-else>{{ title || label }}</template>
                </template>
              </LegoTreeSelect>
            </FormItem>
          </Col>
          <Col :span="12">
            <FormItem label="labelInValue false" v-bind="form.validateInfos.rangeCodeAll">
              <LegoTreeSelect
                :treeData="state.treeDataAll"
                :loading="state.loadingAll"
                v-model:value="modelRef.rangeCodeAll"
                :labelInValue="false"
              ></LegoTreeSelect>
            </FormItem>
          </Col>
          <Col :span="6">
            <Button @click="onQuery" style="margin-left: 10px" type="primary">查询</Button>
          </Col>
        </Row>
      </Form>
    </div>
  </ConfigProvider>
</template>

<script setup lang="ts">
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import type { Rule } from 'ant-design-vue/es/form';
import { ConfigProvider, Form, FormItem, Row, Col, Button } from 'ant-design-vue';
import { reactive, onMounted, ref } from 'vue';
import { LegoTreeSelect, LegoTreeUtils, LegoTreeTypes, LegoTreeServices } from '@pluve/lego-tree-vue';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');

const treeRef = ref();
const treeRefAll = ref();

// 查询条件 ref
const modelRef = reactive<{
  rangeCodeObj?: LegoTreeTypes.ITreeSelectLabelInValue;
  rangeCodeObjAll?: LegoTreeTypes.ITreeSelectLabelInValue;
  rangeCode?: string;
  rangeCodeAll?: string;
}>({
  rangeCodeObj: undefined,
  rangeCodeObjAll: undefined,
  rangeCode: '5024',
  rangeCodeAll: '10004575_5024', // 全量数据源时:需要使用 code_storeCode 格式
});

const rulesRef: Record<string, Rule[]> = reactive({
  rangeCodeObj: [
    {
      required: true,
      validator: (_rule: Rule, value?: LegoTreeTypes.ITreeSelectLabelInValue) => {
        if (!value) {
          return Promise.reject(new Error('请选择'));
        }
        return Promise.resolve();
      },
    },
  ],
  rangeCodeObjAll: [
    {
      required: false,
    },
  ],
  rangeCode: [
    {
      required: false,
    },
  ],
  rangeCodeAll: [
    {
      required: true,
      validator: (_rule: Rule, value?: string) => {
        if (!value) {
          return Promise.reject(new Error('请选择'));
        }
        return Promise.resolve();
      },
    },
  ],
});

// 查询条件 form
const form = Form.useForm(modelRef, rulesRef);

const state = reactive<{
  treeData: LegoTreeTypes.ITreeNodesType[];
  treeDataAll: LegoTreeTypes.ITreeNodesType[];
  loading: boolean;
  loadingAll: boolean;
}>({
  treeData: [],
  treeDataAll: [],
  loading: false,
  loadingAll: false,
});

// 获取 SSO 有效数据源
const loadOrgTreeNodeByLevel = async () => {
  state.loading = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].sub || [],
      pid: undefined,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeData = newTreeData;
  } catch (error) {
    state.treeData = [];
  } finally {
    state.loading = false;
  }
};

// 获取账号中心全量数据源
const loadOrgTreeNodeByLevelAll = async () => {
  state.loadingAll = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
      jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
      jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].children || [],
      pid: undefined,
      childrenKey: 'children',
      hierarchyKey: 'rank',
      includeDisabledData: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeDataAll = newTreeData;
  } catch (error) {
    state.treeDataAll = [];
  } finally {
    state.loadingAll = false;
  }
};

onMounted(() => {
  loadOrgTreeNodeByLevel();
  loadOrgTreeNodeByLevelAll();
});

const onQuery = () => {
  console.log('treeRef.value', treeRef.value?.getTreeInfo());
  console.log('treeRefAll.value', treeRefAll.value?.getTreeInfo());

  form
    .validate()
    .then(() => {
      console.log('查询条件:', modelRef);
    })
    .catch((err) => {
      console.log('err->', err);
    });
};

// 接口请求成功后返回对应的树数据
const getTreeInfo = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfo', { treeData, dataListMap });
};

// 接口请求成功后返回对应的树数据
const getTreeInfoAll = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfoAll', { treeData, dataListMap });
};
</script>

<style lang="less" module>
.wrap {
  position: relative;
  background-color: #fff;
  padding: 40px 80px;
  border-radius: 4px;
}
</style>

LegoLeftTree

通常用于页面左右侧布局,左侧展示组织机构树,右侧展示业务内容。左侧操作后右侧同步更新。

export interface ILegoLeftTreeProps
  extends Omit<
    TreeProps,
    | 'blockNode'
    | 'fieldNames'
    | 'multiple'
    | 'treeData'
    | 'virtual'
    | 'checkable'
    | 'selectable'
    | 'checkedKeys'
    | 'selectedKeys'
    | 'autoExpandParent'
    | 'expandedKeys'
    | 'height'
  > {
  envType?: 'test' | 'prod';
  jsonFrom?: OrgTreeJsonFromEnum;
  jsonType?: OrgTreeJsonTypeEnum;
  hierarchyList?: string[];
  selectHierarchy?: string;
  enableSelectedHierarchy?: string[];

  treeData?: LegoTreeTypes.ITreeNodesType[];
  loading?: boolean;
  dataListMap?: Record<string, LegoTreeTypes.ITreeNodesType>;
  request?: () => Promise<LegoTreeTypes.ITreeNodesType[]>;

  value?: string | string[] | LegoTreeTypes.ICheckedAreaItem[];
  headerTitle?: string;
  placeholder?: string;
  disabled?: boolean;
  selectType?: 'single' | 'multiple';
  includeArea?: boolean;
  width?: number | string;
  virtualHeight?: number;
  showSwitch?: boolean;
  defaultIsShow?: boolean;

  keepOriginNode?: boolean;
}

API

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | envType | 环境(测试 | 生产)。走组件默认接口请求时使用,其余场景无需传该字段 | test \| prod | 是 | test | | jsonFrom | 组织机构树 json 来源。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonFromEnum | 是 | LegoTreeTypes.OrgTreeJsonFromEnum.SSO | | jsonType | 组织机构树 json 类型:全量(包含已关停等) or 有效的。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonTypeEnum | 是 | LegoTreeTypes.OrgTreeJsonTypeEnum.ENABLE | | hierarchyList | 组织机构级别数组,01-07,用于过滤不包含的节点信息,没传或为空则不过滤。走组件默认接口请求时使用,其余场景无需传该字段 | string[] | 是 | - | | selectHierarchy | 选择的组织机构级别,01-07,用于过滤子节点不包含 selectHierarchy 层级的节点。走组件默认接口请求时使用,其余场景无需传该字段 | string | 是 | - | | enableSelectedHierarchy | 【单选】可选择的组织机构级别,不传或为空则都可以选择,传了则包含的级别才可以选择,否则 selectablefalse 不可选择 | string[] | 是 | [] | | treeData | antd vue TreeSelect 组件数据源(已经经过转化后的数据源) | LegoTreeTypes.ITreeNodesType[] | 是 | - | | loading | 外部数据源加载时的 loading 状态 | boolean | 是 | false | | dataListMap | 扁平化 Map(已经经过转化后的数据源生成的 map) | Record<string, LegoTreeTypes.ITreeNodesType> | 是 | {} | | request | 自定义服务,用于获取组件树数据源 | () => Promise<LegoTreeTypes.ITreeNodesType[]> | 是 | - | | value(v-model) | 当前选中的值,区分单选、多选门店、多选节点三种类型 | string \| string[] \| LegoTreeTypes.ICheckedAreaItem[] | 是 | - | | headerTitle | 头部标题文案 | string | 是 | 选择门店 | | placeholder | Input.Search 输入框 placeholder | string | 是 | 搜索门店名称或编码 | | disabled | tree 选择是否禁用 | boolean | 是 | false | | selectType | 选择类型:单选/多选 | single \| multiple | 是 | 'single' | | includeArea | 【多选】是否包含区域选择,即是否允许选择各层级父子节点 | boolean | 是 | false | | width | 组件宽度 | string \| number | 是 | 280 | | virtualHeight | 组件 Tree 虚拟滚动高度 | number | 是 | 820 | | showSwitch | 是否展示 展开收起功能 开关 | boolean | 是 | true | | defaultIsShow | 默认是否处于展开状态 | boolean | 是 | true | | keepOriginNode | 是否树节点保留原始对象信息。若开启,配合 includeAreatrue,则会返回 originNode(原始对象信息) | boolean | 是 | false | | title | 自定义标题,在原始基础上,增加当前搜索值 searchValue 字段,供自定义判断处理 | slot({ key, title, ..., searchValue }) | 是 | - |

事件(emit)

| 事件名称 | 说明 | 回调参数 | | --- | --- | --- | | change | 选中/反选树节点时调用此函数 | function(string \| string[] \| LegoTreeTypes.ICheckedAreaItem[]) | | switch | 展开/收起按钮切换时调用此函数 | function(boolean) | | checkBefore | Tree onCheck 触发时优先调用此函数,此函数第三个参数返回 组件内部的树数据源 | function(checked: Key[] \| { checked: Key[]; halfChecked: Key[]; }, info: CheckInfo, treeInfo: { treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType> }) | | getTreeInfo | 接口请求完成后,调用此函数,返回当前组件内部的树数据源 | function({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

ref

通过 ref 获取组件提供的方法或属性。以下是提供的方法或属性: | 方法/属性名称 | 说明 | 类型 | | --- | --- | --- | | getTreeInfo | 【方法】提供组件内部的树数据源 | () => ({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

效果展示

测试环境地址:https://yf-test-oss.yifengx.com/webtest/yangwen/legoTree/index.html#/leftTree

使用示例

<template>
  <div :class="$style.top">
    <RadioGroup v-model:value="state.dataType">
      <Radio value="all">全量(包含已关停)</Radio>
      <Radio value="enable">有效</Radio>
    </RadioGroup>
    <Button type="primary" size="small" @click="getTreeInfoWithRef" style="margin-left: 50px">
      点我获取tree数据源
    </Button>
  </div>

  <!-- 有效数据源 -->
  <div :class="$style.wrap" :style="{ display: state.dataType === 'all' ? 'none' : 'flex' }">
    <LegoLeftTree
      env-type="prod"
      :hierarchyList="['01', '02', '03', '04', '06', '07']"
      selectHierarchy="07"
      :enable-selected-hierarchy="['01', '07']"
      header-title="选择范围"
      placeholder="搜索范围名称或编码"
      v-model:value="state.storeCode"
      :show-switch="false"
      ref="treeRef"
      @get-tree-info="getTreeInfo"
      @change="(val) => onChange1(val)"
    >
      <template #title="{ value: val, title, label }">
        <div>
          <span style="margin-right: 10px">{{ title || label }}</span>
          <exclamation-circle-outlined @click="onIconClick1(val)" />
        </div>
      </template>
    </LegoLeftTree>

    <div style="margin-left: 20px; width: auto; height: 100%">
      <LegoLeftTree
        :request="customRequest"
        v-model:value="state.storeCodes"
        header-title="选择渠道"
        placeholder="搜索渠道名称或编码"
        select-type="multiple"
        width="300px"
        :default-is-show="true"
        @switch="onSwitch2"
        @change="(val) => onChange2(val)"
      >
        <template #title="{ value: val, title, label, searchValue }">
          <div>
            <span
              style="margin-right: 10px"
              :style="{ color: searchValue === val ? 'red' : '#222222' }"
            >
              {{ title || label }}
            </span>
            <up-circle-outlined @click="onIconClick2(val)" />
          </div>
        </template>
      </LegoLeftTree>
    </div>

    <div style="margin-left: 20px; width: auto; height: 100%">
      <LegoLeftTree
        :treeData="state.treeData"
        :loading="state.loading"
        v-model:value="state.storeNodes"
        header-title="选择组织机构"
        select-type="multiple"
        include-area
        :virtual-height="600"
        :default-is-show="false"
        :keep-origin-node="true"
        @switch="onSwitch3"
        @change="(val) => onChange3(val)"
        @check-before="onCheckBefore"
      />
    </div>

    <div :class="$style.right">我是右侧业务内容</div>
  </div>

  <!-- 账号中心 && 全部数据源(包含已关停) -->
  <div :class="$style.wrap" :style="{ display: state.dataType === 'all' ? 'flex' : 'none' }">
    <LegoLeftTree
      :json-from="LegoTreeTypes.OrgTreeJsonFromEnum.USER"
      :json-type="LegoTreeTypes.OrgTreeJsonTypeEnum.ALL"
      env-type="test"
      :hierarchyList="['01', '02', '03', '04', '06', '07']"
      selectHierarchy="07"
      :enable-selected-hierarchy="['01', '07']"
      header-title="选择范围"
      placeholder="搜索范围名称或编码"
      v-model:value="state.storeCodeAll"
      :show-switch="false"
      ref="treeRefAll"
      @get-tree-info="getTreeInfoAll"
      @change="(val) => onChange1(val)"
    >
      <template #title="{ value: val, title, label }">
        <div>
          <span style="margin-right: 10px">{{ title || label }}</span>
          <exclamation-circle-outlined @click="onIconClick1(val)" />
        </div>
      </template>
    </LegoLeftTree>

    <div style="margin-left: 20px; width: auto; height: 100%">
      <LegoLeftTree
        :treeData="state.treeDataAll"
        :loading="state.loadingAll"
        v-model:value="state.storeCodesAll"
        header-title="选择渠道"
        placeholder="搜索渠道名称或编码"
        select-type="multiple"
        width="300px"
        :default-is-show="true"
        @switch="onSwitch2"
        @change="(val) => onChange2(val)"
      >
        <template #title="{ value: val, title, label, searchValue }">
          <div>
            <span
              style="margin-right: 10px"
              :style="{ color: searchValue === LegoTreeUtils.getRealKey(val) ? 'red' : '#222222' }"
            >
              {{ title || label }}
            </span>
            <up-circle-outlined @click="onIconClick2(val)" />
          </div>
        </template>
      </LegoLeftTree>
    </div>

    <div style="margin-left: 20px; width: auto; height: 100%">
      <LegoLeftTree
        :treeData="state.treeDataAll"
        :loading="state.loadingAll"
        v-model:value="state.storeNodesAll"
        header-title="选择组织机构"
        select-type="multiple"
        include-area
        :virtual-height="600"
        :default-is-show="false"
        :keep-origin-node="true"
        @switch="onSwitch3"
        @change="(val) => onChange3(val)"
        @check-before="onCheckBeforeAll"
      />
    </div>

    <div :class="$style.right">我是右侧业务内容</div>
  </div>
</template>

<script setup lang="ts">
import { UpCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { ConfigProvider, Button, RadioGroup, Radio } from 'ant-design-vue';
import { reactive, onMounted, ref } from 'vue';
import { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
import { LegoLeftTree, LegoTreeUtils, LegoTreeTypes, LegoTreeServices } from '@pluve/lego-tree-vue';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
import { originList } from '@/constant/channel';

const treeRef = ref();
const treeRefAll = ref();

const state = reactive<{
  dataType: 'all' | 'enable';
  treeData: LegoTreeTypes.ITreeNodesType[];
  treeDataAll: LegoTreeTypes.ITreeNodesType[];
  loading: boolean;
  loadingAll: boolean;
  storeCode?: string;
  storeCodeAll?: string;
  storeCodes: string[];
  storeCodesAll: string[];
  storeNodes: LegoTreeTypes.ICheckedAreaItem[];
  storeNodesAll: LegoTreeTypes.ICheckedAreaItem[];
}>({
  dataType: 'enable',
  treeData: [],
  treeDataAll: [],
  loading: false,
  loadingAll: false,
  storeCode: '5500',
  storeCodeAll: '10010357_5500',
  storeCodes: ['5500'],
  storeCodesAll: ['10010357_5500'],
  storeNodes: [{ key: '5500', hierarchy: '07' }],
  storeNodesAll: [{ key: '10010357_5500', code: '10010357', storeCode: '5500', hierarchy: '07' }],
});

const customRequest = async () => {
  try {
    const result: any[] = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(originList);
      }, 800);
    });
    const convertedData = LegoTreeUtils.recursionTreeData({
      list: result ?? [],
      valKey: 'id',
      nameKey: 'text',
      childrenKey: 'children',
    });
    return convertedData;
  } catch (error) {
    return [];
  }
};

const onChange1 = (val: string) => {
  console.log('单选选择范围 val:', val);
};

const onChange2 = (val: string[]) => {
  console.log('多选选择渠道 val:', val);
};

const onChange3 = (val: LegoTreeTypes.ICheckedAreaItem[]) => {
  console.log('多选选择组织机构 val:', val);
};

const onSwitch2 = (bool: boolean) => {
  console.log('多选选择渠道,当前展开收起状态:', bool);
};

const onSwitch3 = (bool: boolean) => {
  console.log('多选选择组织机构,当前展开收起状态:', bool);
};

const onIconClick1 = (val: string) => {
  console.log('单选选择范围,点击图标后获取对应的 value:', val);
};

const onIconClick2 = (val: string) => {
  console.log('多选选择渠道,点击图标后获取对应的 value:', val);
};

const loadOrgTreeNodeByLevel = async () => {
  state.loading = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].sub || [],
      pid: undefined,
      keepOriginNode: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeData = newTreeData;
  } catch (error) {
    state.treeData = [];
  } finally {
    state.loading = false;
  }
};

const loadOrgTreeNodeByLevelAll = async () => {
  state.loadingAll = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
      jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
      jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].children || [],
      pid: undefined,
      childrenKey: 'children',
      hierarchyKey: 'rank',
      keepOriginNode: true,
      includeDisabledData: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeDataAll = newTreeData;
  } catch (error) {
    state.treeDataAll = [];
  } finally {
    state.loadingAll = false;
  }
};

onMounted(() => {
  loadOrgTreeNodeByLevel();
  loadOrgTreeNodeByLevelAll();
});

// 接口请求成功后返回对应的树数据
const getTreeInfo = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfo', { treeData, dataListMap });
};

// 接口请求成功后返回对应的树数据
const getTreeInfoAll = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfoAll', { treeData, dataListMap });
};

const getTreeInfoWithRef = () => {
  if (state.dataType === 'enable') {
    console.log('treeRef.value', treeRef.value?.getTreeInfo());
  } else {
    console.log('treeRefAll.value', treeRefAll.value?.getTreeInfo());
  }
};

const onCheckBefore = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfo', treeInfo);
};

const onCheckBeforeAll = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfoAll', treeInfo);
};
</script>

<style lang="less" module>
body {
  background-color: #f1f4f5;
  width: 100%;
  height: 100%;
}

.top {
  position: relative;
  padding: 10px 50px 0;
}

.wrap {
  position: relative;
  min-height: 52px;
  background-color: #f1f4f5;
  padding: 10px 50px;
  border-radius: 4px;
  height: calc(100vh - 120px);
  display: flex;
  align-items: center;
  justify-content: flex-start;
}

.right {
  width: 300px;
  height: 100%;
  background: #fff;
  margin-left: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

LegoTreeTransfer

通常作为最底层组件向外提供,可以嵌在 tab 内、Modal 弹窗内等任何场景,左右侧布局展示。props 继承自 antd vue TreeProps

export interface ILegoTreeTransferProps
  extends Omit<
    TreeProps,
    | 'blockNode'
    | 'fieldNames'
    | 'multiple'
    | 'treeData'
    | 'virtual'
    | 'checkable'
    | 'selectable'
    | 'checkedKeys'
    | 'selectedKeys'
    | 'onSelect'
    | 'autoExpandParent'
    | 'expandedKeys'
    | 'height'
  > {
  envType?: 'test' | 'prod';
  jsonFrom?: OrgTreeJsonFromEnum;
  jsonType?: OrgTreeJsonTypeEnum;
  hierarchyList?: string[];
  selectHierarchy?: string;

  treeData?: LegoTreeTypes.ITreeNodesType[];
  loading?: boolean;
  dataListMap?: Record<string, LegoTreeTypes.ITreeNodesType>;
  request?: () => Promise<LegoTreeTypes.ITreeNodesType[]>;

  value: string[] | LegoTreeTypes.ICheckedAreaItem[];
  placeholder?: string;
  disabled?: boolean;
  includeArea?: boolean;
  searchDomHeight?: number;
  customFormatName?: (
    value: (LegoTreeTypes.ICheckedAreaItem | string)[],
    type: 'input' | 'modal'
  ) => string | undefined;

  keepOriginNode?: boolean;
}

API

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | envType | 环境(测试 | 生产)。走组件默认接口请求时使用,其余场景无需传该字段 | test \| prod | 是 | test | | jsonFrom | 组织机构树 json 来源。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonFromEnum | 是 | LegoTreeTypes.OrgTreeJsonFromEnum.SSO | | jsonType | 组织机构树 json 类型:全量(包含已关停等) or 有效的。走组件默认接口请求时使用,其余场景无需传该字段 | LegoTreeTypes.OrgTreeJsonTypeEnum | 是 | LegoTreeTypes.OrgTreeJsonTypeEnum.ENABLE | | hierarchyList | 组织机构级别数组,01-07,用于过滤不包含的节点信息,没传或为空则不过滤。走组件默认接口请求时使用,其余场景无需传该字段 | string[] | 是 | - | | selectHierarchy | 选择的组织机构级别,01-07,用于过滤子节点不包含 selectHierarchy 层级的节点。走组件默认接口请求时使用,其余场景无需传该字段 | string | 是 | - | | treeData | antd vue TreeSelect 组件数据源(已经经过转化后的数据源,若 includeAreatrue,则子节点需要包含 trace 字段) | LegoTreeTypes.ITreeNodesType[] | 是 | - | | loading | 外部数据源加载时的 loading 状态 | boolean | 是 | false | | dataListMap | 扁平化 map(已经经过转化后的数据源生成的 map) | Record<string, LegoTreeTypes.ITreeNodesType> | 是 | {} | | request | 自定义服务,用于获取组件树数据源 | () => Promise<LegoTreeTypes.ITreeNodesType[]> | 是 | - | | value(v-model) | 当前选中值,区分选择父子节点和子节点,有 2 种类型 | string[] \| LegoTreeTypes.ICheckedAreaItem[] | 否 | [] | | placeholder | Input.Search 输入框的 placeholder | string | 是 | '搜索关键词' | | disabled | tree 选择是否禁用 | boolean | 是 | false | | includeArea | 是否包含区域选择,即是否允许选择各层级父子节点 | boolean | 是 | false | | searchDomHeight | 实际业务中搜索输入框的高度,不同的样式主题高度会有差别 | number | 是 | 46 | | customFormatName | 自定义选中项内容展示,包含 input 输入框的回显和 Tree 选中后的汇总 | (value: (LegoTreeTypes.ICheckedAreaItem \| string)[], type: 'input' \| 'modal') => string \| undefined | 是 | - | | keepOriginNode | 是否树节点保留原始对象信息。若开启,配合 includeAreatrue,则会返回 originNode(原始对象信息) | boolean | 是 | false | | title | 自定义标题,在原始基础上,增加当前搜索值 searchValue 字段,供自定义判断处理 | slot({ key, title, ..., searchValue }) | 是 | - |

事件(emit)

| 事件名称 | 说明 | 回调参数 | | --- | --- | --- | | change | 选中/反选树节点时调用此函数 | function(string[] \| LegoTreeTypes.ICheckedAreaItem[]) | | checkBefore | Tree onCheck 触发时优先调用此函数,此函数第三个参数返回 组件内部的树数据源 | function(checked: Key[] \| { checked: Key[]; halfChecked: Key[]; }, info: CheckInfo, treeInfo: { treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType> }) | | getTreeInfo | 接口请求完成后,调用此函数,返回当前组件内部的树数据源 | function({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

ref

通过 ref 获取组件提供的方法或属性。以下是提供的方法或属性: | 方法/属性名称 | 说明 | 类型 | | --- | --- | --- | | getTreeInfo | 【方法】提供组件内部的树数据源 | () => ({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

效果展示

测试环境地址:https://yf-test-oss.yifengx.com/webtest/yangwen/legoTree/index.html#/treeTransfer

使用示例

<template>
  <ConfigProvider :locale="zh_CN">
    <div :class="$style.wrap">
      <!-- 有效数据源 -->
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeTransfer demo:{{ JSON.stringify(state.storeCodes) }}
      </h4>
      <div style="width: 536px; margin: 30px 0 0">
        <LegoTreeTransfer
          :treeData="state.treeData"
          :loading="state.loading"
          :include-area="true"
          :keep-origin-node="true"
          v-model:value="state.storeCodes"
        />
      </div>
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeTransfer demo:{{ JSON.stringify(state.storeCodes1) }}
      </h4>
      <Button type="primary" @click="getTreeInfoWithRef" style="margin-top: 10px">
        点我获取tree数据源
      </Button>
      <div style="width: 536px; margin: 30px 0 0">
        <LegoTreeTransfer
          :include-area="false"
          v-model:value="state.storeCodes1"
          :custom-format-name="customFormatName"
          :request="customRequest"
          ref="treeRef"
          @get-tree-info="getTreeInfo"
          @check-before="onCheckBefore"
        />
      </div>

      <!-- 账号中心 && 全部数据源(包含已关停) -->
      <div style="font-weight: bold; color: red; margin-top: 10px; margin-bottom: 10px">
        以下数据源为账号中心全量数据源(包含已关停数据):
      </div>
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeTransfer demo:{{ JSON.stringify(state.storeCodesAll) }}
      </h4>
      <div style="width: 536px; margin: 30px 0 0">
        <LegoTreeTransfer
          :treeData="state.treeDataAll"
          :loading="state.loadingAll"
          :include-area="true"
          :keep-origin-node="true"
          v-model:value="state.storeCodesAll"
        />
      </div>
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeTransfer demo:{{ JSON.stringify(state.storeCodes1All) }}
      </h4>
      <Button type="primary" @click="getTreeInfoWithRefAll" style="margin-top: 10px">
        点我获取tree数据源
      </Button>
      <div style="width: 536px; margin: 30px 0 0">
        <LegoTreeTransfer
          :json-from="LegoTreeTypes.OrgTreeJsonFromEnum.USER"
          :json-type="LegoTreeTypes.OrgTreeJsonTypeEnum.ALL"
          env-type="test"
          :hierarchyList="['01', '02', '03', '04', '06', '07']"
          selectHierarchy="07"
          :include-area="false"
          v-model:value="state.storeCodes1All"
          ref="treeRefAll"
          @get-tree-info="getTreeInfoAll"
          @check-before="onCheckBeforeAll"
        />
      </div>
    </div>
  </ConfigProvider>
</template>

<script setup lang="ts">
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { ConfigProvider, Button } from 'ant-design-vue';
import { reactive, onMounted, ref } from 'vue';
import { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
import { LegoTreeTransfer, LegoTreeUtils, LegoTreeTypes, LegoTreeServices } from '@pluve/lego-tree-vue';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
import { originList } from '@/constant/channel';

const treeRef = ref();
const treeRefAll = ref();

const state = reactive<{
  treeData: LegoTreeTypes.ITreeNodesType[];
  treeDataAll: LegoTreeTypes.ITreeNodesType[];
  loading: boolean;
  loadingAll: boolean;
  storeCodes: LegoTreeTypes.ICheckedAreaItem[];
  storeCodesAll: LegoTreeTypes.ICheckedAreaItem[];
  storeCodes1: string[];
  storeCodes1All: string[];
}>({
  treeData: [],
  treeDataAll: [],
  loading: false,
  loadingAll: false,
  storeCodes: [],
  storeCodesAll: [],
  storeCodes1: [],
  storeCodes1All: [],
});

const loadOrgTreeNodeByLevel = async () => {
  state.loading = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].sub || [],
      pid: undefined,
      keepOriginNode: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeData = newTreeData;
  } catch (error) {
    state.treeData = [];
  } finally {
    state.loading = false;
  }
};

const loadOrgTreeNodeByLevelAll = async () => {
  state.loadingAll = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
      jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
      jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].children || [],
      pid: undefined,
      childrenKey: 'children',
      hierarchyKey: 'rank',
      keepOriginNode: true,
      includeDisabledData: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeDataAll = newTreeData;
  } catch (error) {
    state.treeDataAll = [];
  } finally {
    state.loadingAll = false;
  }
};

onMounted(() => {
  loadOrgTreeNodeByLevel();
  loadOrgTreeNodeByLevelAll();
});

const customRequest = async () => {
  try {
    const result: any[] = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(originList);
      }, 800);
    });
    const convertedData = LegoTreeUtils.recursionTreeData({
      list: result ?? [],
      valKey: 'id',
      nameKey: 'text',
      childrenKey: 'children',
    });
    return convertedData;
  } catch (error) {
    return [];
  }
};

const customFormatName = (
  values: (LegoTreeTypes.ICheckedAreaItem | string)[],
  type: 'input' | 'modal'
) => {
  const checkedStr = (values as LegoTreeTypes.ICheckedAreaItem[])
    .map((item) => item.title)
    .join(',');

  if (type === 'input') {
    return checkedStr ? `${checkedStr}被选择` : undefined;
  }

  return checkedStr ? `已选:${checkedStr}` : '已选:0条数据';
};

// 接口请求成功后返回对应的树数据
const getTreeInfo = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfo', { treeData, dataListMap });
};

// 接口请求成功后返回对应的树数据
const getTreeInfoAll = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfoAll', { treeData, dataListMap });
};

const onCheckBefore = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfo', treeInfo);
};

const onCheckBeforeAll = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfoAll', treeInfo);
};

const getTreeInfoWithRef = () => {
  console.log('treeRef.value', treeRef.value?.getTreeInfo());
};

const getTreeInfoWithRefAll = () => {
  console.log('treeRefAll.value', treeRefAll.value?.getTreeInfo());
};
</script>

<style lang="less" module>
.wrap {
  position: relative;
  min-height: 52px;
  background-color: #fff;
  padding: 0 80px 40px;
  border-radius: 4px;
}
</style>

LegoTreeModal

通常作为 选择组织机构树的弹窗使用,Modal 内部引用 LegoTreeTransfer 组件来实现。故部分属性可以参考 LegoTreeTransfer 组件。

export interface ILegoTreeModalProps {
  title?: string;
  visible?: boolean;
  value: string[] | LegoTreeTypes.ICheckedAreaItem[];
  okLoading?: boolean;
  onOk?: (value: string[] | LegoTreeTypes.ICheckedAreaItem[]) => void;
  onCancel?: () => void;
  disabled?: boolean;
  showStoreUploadBtn?: boolean;
  modalProps?: Omit<
    ModalProps,
    'onOk' | 'onCancel' | 'visible' | 'destroyOnClose' | 'closable' | 'keyboard' | 'maskClosable'
  >;
  transferProps?: Omit<ILegoTreeTransferProps, 'value' | 'disabled'>;
}

API

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | title | Modal 弹窗的 title,优先级:title[slot] > modalProps.title > title[string] | string \| slot | 是 | 选择区域 | | visible | 控制 Modal 弹窗的显隐 | boolean | 是 | false | | value(v-model) | 当前选中值,区分选择父子节点和子节点,有 2 种类型 | string[] \| LegoTreeTypes.ICheckedAreaItem[] | 否 | [] | | okLoading | 点击弹窗确定按钮的 loading | boolean | 是 | false | | onOk | 点击弹窗确定按钮触发 | (value: string[] \| LegoTreeTypes.ICheckedAreaItem[]) => void | 是 | - | | onCancel | 点击弹窗取消按钮触发 | () => void | 是 | - | | disabled | Modal 弹窗 和 tree 选择是否禁用 | boolean | 是 | false | | showStoreUploadBtn | 是否展示默认的导入门店按钮,导入任意节点,做全量覆盖处理。注意:1. 如果走全量数据源(包含已关停数据),则不能使用默认的导入功能;2. 如果开启 includeArea,则不区分导入的数据之间是否存在父子层级依赖关系,一并去重导入; | boolean | 是 | false | | modalProps | antd vue Modal 弹窗组件的额外的属性 | Omit<ModalProps, 'onOk' \| 'onCancel' \| 'visible' \| 'destroyOnClose' \| 'closable' \| 'keyboard' \| 'maskClosable'> | 是 | - | | transferProps | LegoTreeTransfer 组件的额外的属性 | Omit<ILegoTreeTransferProps, 'value' \| 'disabled'> | 是 | {} | | footer | Modal 弹窗的 footer,优先级最高 | slot | 是 | - | | renderFooterLeft | Modal 弹窗底部左侧的自定义区域 | slot | 是 | - | | renderUpload | Modal 弹窗底部右侧的自定义导入功能,disabledfalse 时展示,优先级高于默认的导入功能 | slot | 是 | - | | bodyTop | Modal 弹窗 LegoTreeTransfer 组件上方的自定义区域 | slot | 是 | - | | bodyBottom | Modal 弹窗 LegoTreeTransfer 组件下方的自定义区域 | slot | 是 | - | | treeTitle | 用于传递给 LegoTreeTransfer 组件的 title 插槽(避免插槽名字冲突) | slot | 是 | - |

事件(emit)

| 事件名称 | 说明 | 回调参数 | | --- | --- | --- | | getTreeInfo | 接口请求完成后,调用此函数,返回当前组件内部的树数据源 | function({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

ref

通过 ref 获取组件提供的方法或属性。以下是提供的方法或属性: | 方法/属性名称 | 说明 | 类型 | | --- | --- | --- | | getTreeInfo | 【方法】提供组件内部的树数据源 | () => ({ treeData: LegoTreeTypes.ITreeNodesType[]; dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>; }) |

效果展示

测试环境地址:https://yf-test-oss.yifengx.com/webtest/yangwen/legoTree/index.html#/treeModal

使用示例

<template>
  <ConfigProvider :locale="zh_CN">
    <div :class="$style.wrap">
      <!-- 有效数据源 -->
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeModal demo:{{ JSON.stringify(state.storeCodes) }}
      </h4>
      <Button style="margin-top: 20px" type="primary" @click="showTreeModal">
        点我打开组织机构树弹窗
      </Button>
      <LegoTreeModal
        :visible="state.treeModalVisible"
        v-model:value="state.storeCodes"
        @cancel="
          () => {
            state.treeModalVisible = false;
          }
        "
        @ok="
          (value) => {
            state.treeModalVisible = false;
          }
        "
        :showStoreUploadBtn="true"
        :transfer-props="{
          hierarchyList: ['01', '02', '03', '04', '06', '07'],
          selectHierarchy: '07',
          includeArea: false,
          onCheck: onTreeCheck,
        }"
        ref="treeRef"
        @get-tree-info="getTreeInfo"
      >
        <template #title>我是 LegoTreeModal 弹窗组件的自定义 title</template>
        <template #bodyTop>我是 LegoTreeModal 弹窗组件的自定义 bodyTop</template>
        <template #bodyBottom>我是 LegoTreeModal 弹窗组件的自定义 bodyBottom</template>
        <template #renderFooterLeft>我是 LegoTreeModal 弹窗组件的自定义 renderFooterLeft</template>
      </LegoTreeModal>

      <!-- 账号中心 && 全部数据源(包含已关停) -->
      <div style="font-weight: bold; color: red; margin-top: 40px; margin-bottom: 0px">
        以下数据源为账号中心全量数据源(包含已关停数据):
      </div>
      <h4
        style="margin: 30px 50px 0 0; word-break: break-word; max-height: 100px; overflow-y: auto"
      >
        LegoTreeModal demo:{{ JSON.stringify(state.storeCodesAll) }}
      </h4>
      <Button style="margin-top: 20px" type="primary" @click="showTreeModalAll">
        点我打开组织机构树弹窗
      </Button>
      <LegoTreeModal
        :visible="state.treeModalVisibleAll"
        v-model:value="state.storeCodesAll"
        @cancel="
          () => {
            state.treeModalVisibleAll = false;
          }
        "
        @ok="
          (value) => {
            state.treeModalVisibleAll = false;
          }
        "
        :showStoreUploadBtn="false"
        :transfer-props="{
          jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
          jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
          hierarchyList: ['01', '02', '03', '04', '06', '07'],
          selectHierarchy: '07',
          includeArea: false,
          onCheck: onTreeCheckAll,
        }"
        ref="treeRefAll"
        @get-tree-info="getTreeInfoAll"
      >
        <template #title>我是 LegoTreeModal 弹窗组件的自定义 title</template>
        <template #bodyTop>我是 LegoTreeModal 弹窗组件的自定义 bodyTop</template>
        <template #bodyBottom>我是 LegoTreeModal 弹窗组件的自定义 bodyBottom</template>
        <template #renderFooterLeft>我是 LegoTreeModal 弹窗组件的自定义 renderFooterLeft</template>
      </LegoTreeModal>
    </div>
  </ConfigProvider>
</template>

<script setup lang="ts">
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { ConfigProvider, Button } from 'ant-design-vue';
import { reactive, ref } from 'vue';
import { LegoTreeModal, LegoTreeTypes, LegoTreeUtils, LegoTreeServices } from '@pluve/lego-tree-vue';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');

const treeRef = ref();
const treeRefAll = ref();

const state = reactive<{
  storeCodes: string[];
  storeCodesAll: string[];
  treeModalVisible: boolean;
  treeModalVisibleAll: boolean;
}>({
  storeCodes: [],
  storeCodesAll: [],
  treeModalVisible: false,
  treeModalVisibleAll: false,
});

const showTreeModal = () => {
  state.treeModalVisible = true;
};

const showTreeModalAll = () => {
  state.treeModalVisibleAll = true;
};

// 接口请求成功后返回对应的树数据
const getTreeInfo = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfo', { treeData, dataListMap });
};

// 接口请求成功后返回对应的树数据
const getTreeInfoAll = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfoAll', { treeData, dataListMap });
};

const onTreeCheck = () => {
  console.log('treeRef.value', treeRef.value?.getTreeInfo());
};

const onTreeCheckAll = () => {
  console.log('treeRefAll.value', treeRefAll.value?.getTreeInfo());
};
</script>

<style lang="less" module>
.wrap {
  position: relative;
  min-height: 52px;
  background-color: #fff;
  padding: 20px 80px;
  border-radius: 4px;
}
</style>

LegoTree

通常用于多选组织架构,与 FormItem 进行绑定,内部引用 LegoTreeModal 组件来实现。故部分属性可以参考 LegoTreeModal 组件。

export interface ILegoTreeProps
  extends Omit<
    ILegoTreeModalProps,
    'title' | 'visible' | 'value' | 'onOk' | 'onCancel' | 'disabled'
  > {
  title?: string;
  value: string[] | LegoTreeTypes.ICheckedAreaItem[];
  disabled?: boolean;
  placeholder?: string;
  limitSelectedNum?: number;
  onlyStoreCodeInDisabled?: boolean;
  showModalInDisabled?: boolean;
  customValidate?: (value: string[] | LegoTreeTypes.ICheckedAreaItem[]) => Promise<boolean>;
  customFormatName?: (
    value: (LegoTreeTypes.ICheckedAreaItem | string)[],
    type: 'input' | 'modal'
  ) => string | undefined;
}

API

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | title | Modal 弹窗的 title | string \| slot | 是 | 选择区域 | | value(v-model) | 当前选中值,区分选择父子节点和子节点,有 2 种类型 | string[] \| LegoTreeTypes.ICheckedAreaItem[] | 否 | [] | | disabled | InputModal 弹窗 和 tree 选择是否禁用 | boolean | 是 | false | | placeholder | Input 输入框的 placeholder | string | 是 | '请选择区域' | | limitSelectedNum | 限制最大门店勾选数量,仅限 transferProps.includeArea 为 false 时 | number | 是 | - | | onlyStoreCodeInDisabled | 禁用时,是否只展示门店编码,仅限输入框中展示用 | boolean | 是 | false | | showModalInDisabled | 禁用时,是否可以展示弹窗 | boolean | 是 | true | | customValidate | 点击弹窗确定按钮,触发自定义校验,优先级:customValidate > limitSelectedNum | (value: string[] \| ICheckedAreaItem[]) => Promise<boolean> | 是 | - | | customFormatName | 自定义选中项内容展示,包含 input 输入框的回显和 Tree 选中后的汇总 | (value: (LegoTreeTypes.ICheckedAreaItem \| string)[], type: 'input' \| 'modal') => string \| undefined | 是 | - | | default | 默认插槽,用于自定义展示形式,默认为 Input | slot({ show }) | 是 | - |

事件(emit)

| 事件名称 | 说明 | 回调参数 | | --- | --- | --- | | change | 弹窗选中/反选树节点后,点击【确定】按钮时调用此函数 | function(string[] \| LegoTreeTypes.ICheckedAreaItem[]) |

效果展示

测试环境地址:https://yf-test-oss.yifengx.com/webtest/yangwen/legoTree/index.html#/tree

使用示例

<template>
  <ConfigProvider :locale="zh_CN">
    <div :class="$style.wrap">
      <Form :colon="false" autocomplete="off">
        <Row>
          <!-- 有效数据源 -->
          <Col :span="12">
            <FormItem label="Tree自定义request">
              <LegoTree
                title="选择渠道"
                placeholder="请选择"
                v-model:value="modelRef.storeCodes"
                :showStoreUploadBtn="true"
                :transfer-props="{
                  request: customRequest,
                  includeArea: false,
                }"
                :custom-format-name="customFormatName"
                :disabled="true"
                :show-modal-in-disabled="true"
              ></LegoTree>
            </FormItem>
          </Col>
          <Col :span="12"></Col>
          <Col :span="12">
            <FormItem label="Tree使用默认json">
              <LegoTree
                title="选择门店"
                placeholder="选择门店"
                v-model:value="modelRef.channelList"
                :limit-selected-num="100"
                :showStoreUploadBtn="true"
                :transfer-props="{
                  jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
                  jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ENABLE,
                  hierarchyList: ['01', '02', '03', '04', '06', '07'],
                  selectHierarchy: '07',
                  includeArea: true,
                  keepOriginNode: true,
                  onCheckBefore,
                }"
                :custom-validate="customValidate"
                :disabled="false"
                :show-modal-in-disabled="true"
                @get-tree-info="getTreeInfo"
              >
                <template #treeTitle="{ key: val, title, label }">
                  <b v-if="val === '5024'" style="color: #08c">{{ title || label }}</b>
                  <template v-else>{{ title || label }}</template>
                </template>
              </LegoTree>
            </FormItem>
          </Col>
          <Col :span="12"></Col>

          <!-- 账号中心 && 全部数据源(包含已关停) -->
          <Col :span="24" style="font-weight: bold; color: red; margin-bottom: 24px">
            以下数据源为账号中心全量数据源(包含已关停数据):
          </Col>
          <Col :span="12">
            <FormItem label="Tree自定义treeData">
              <LegoTree
                title="选择渠道"
                placeholder="请选择"
                v-model:value="modelRef.storeCodesAll"
                :limit-selected-num="100"
                :showStoreUploadBtn="false"
                :transfer-props="{
                  treeData: state.treeDataAll,
                  loading: state.loadingAll,
                  includeArea: false,
                  keepOriginNode: true,
                  onCheckBefore: onCheckBeforeAll,
                }"
                :custom-validate="customValidate"
                :disabled="false"
                @get-tree-info="getTreeInfoAll"
              >
                <template #treeTitle="{ key: val, title, label, storeCode }">
                  <b v-if="val === '10004575_5024'" style="color: #08c">{{ title || label }}</b>
                  <b v-else-if="storeCode === '5500'" style="color: #f90">{{ title || label }}</b>
                  <template v-else>{{ title || label }}</template>
                </template>
              </LegoTree>
            </FormItem>
          </Col>
          <Col :span="12"></Col>
          <Col :span="12">
            <FormItem label="Tree使用默认json">
              <LegoTree
                title="选择门店"
                placeholder="选择门店"
                v-model:value="modelRef.channelListAll"
                :showStoreUploadBtn="false"
                :transfer-props="{
                  jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
                  jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
                  hierarchyList: ['01', '02', '03', '04', '06', '07'],
                  selectHierarchy: '07',
                  includeArea: true,
                  keepOriginNode: true,
                }"
                :disabled="true"
                :show-modal-in-disabled="true"
                :only-store-code-in-disabled="true"
              ></LegoTree>
            </FormItem>
          </Col>
          <Col :span="12"></Col>

          <Col :span="6">
            <Button @click="onQuery" style="margin-left: 10px" type="primary">查询</Button>
          </Col>
        </Row>
      </Form>
    </div>
  </ConfigProvider>
</template>

<script setup lang="ts">
import zh_CN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { ConfigProvider, Form, FormItem, Row, Col, Button } from 'ant-design-vue';
import { reactive, onMounted } from 'vue';
import { CheckInfo } from 'ant-design-vue/es/vc-tree/props';
import { LegoTree, LegoTreeUtils, LegoTreeTypes, LegoTreeServices } from '@pluve/lego-tree-vue';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
import { originList } from '@/constant/channel';

// 查询条件 ref
const modelRef = reactive<{
  storeCodes: string[];
  storeCodesAll: string[];
  channelList: LegoTreeTypes.ICheckedAreaItem[];
  channelListAll: LegoTreeTypes.ICheckedAreaItem[];
}>({
  storeCodes: ['5024', '5032', '5979', 'E520'],
  storeCodesAll: [],
  channelList: [],
  channelListAll: [
    {
      key: '10014212_7971',
      code: '10014212',
      storeCode: '7971',
      hierarchy: '07',
      title: '7971-南昌怡园路店',
    },
  ],
});

const state = reactive<{
  treeDataAll: LegoTreeTypes.ITreeNodesType[];
  loadingAll: boolean;
}>({
  treeDataAll: [],
  loadingAll: false,
});

// 查询条件 form
const form = Form.useForm(modelRef, undefined);

const customValidate = async (e: string[] | LegoTreeTypes.ICheckedAreaItem[]) => {
  await new Promise((resolve) => {
    setTimeout(() => {
      console.log('1秒的自定义校验');
      resolve(true);
    }, 1000);
  });
  return true;
};

const customRequest = async () => {
  try {
    const result: any[] = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(originList);
      }, 800);
    });
    const convertedData = LegoTreeUtils.recursionTreeData({
      list: result ?? [],
      valKey: 'id',
      nameKey: 'text',
      childrenKey: 'children',
    });
    return convertedData;
  } catch (error) {
    return [];
  }
};

const customFormatName = (
  values: (LegoTreeTypes.ICheckedAreaItem | string)[],
  type: 'input' | 'modal'
) => {
  if (type === 'input') {
    const checkedStr = (values as string[]).join(',');
    return checkedStr ? `${checkedStr}被选择` : undefined;
  }

  const checkedStr = (values as LegoTreeTypes.ICheckedAreaItem[])
    .map((item) => item.title)
    .join(',');
  return checkedStr ? `已选:${checkedStr}` : '已选:0条数据';
};

const loadOrgTreeNodeByLevelAll = async () => {
  state.loadingAll = true;
  try {
    const result = await LegoTreeServices.getAllOrgTreeNodesByOss({
      hierarchyList: ['01', '02', '03', '04', '06', '07'],
      envType: 'test',
      jsonFrom: LegoTreeTypes.OrgTreeJsonFromEnum.USER,
      jsonType: LegoTreeTypes.OrgTreeJsonTypeEnum.ALL,
    });
    const newTreeData = LegoTreeUtils.recursionTreeData({
      list: result?.[0].children || [],
      pid: undefined,
      childrenKey: 'children',
      hierarchyKey: 'rank',
      keepOriginNode: true,
      includeDisabledData: true,
    });
    LegoTreeUtils.excludeNode(newTreeData, '07');
    state.treeDataAll = newTreeData;
  } catch (error) {
    state.treeDataAll = [];
  } finally {
    state.loadingAll = false;
  }
};

onMounted(() => {
  loadOrgTreeNodeByLevelAll();
});

const onQuery = () => {
  console.log('查询条件:', modelRef);
};

// 接口请求成功后返回对应的树数据
const getTreeInfo = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfo', { treeData, dataListMap });
};

// 接口请求成功后返回对应的树数据
const getTreeInfoAll = ({
  treeData,
  dataListMap,
}: {
  treeData: LegoTreeTypes.ITreeNodesType[];
  dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
}) => {
  console.log('getTreeInfoAll', { treeData, dataListMap });
};

const onCheckBefore = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfo', treeInfo);
};

const onCheckBeforeAll = (
  _: any,
  e: CheckInfo,
  treeInfo: {
    treeData: LegoTreeTypes.ITreeNodesType[];
    dataListMap: Record<string, LegoTreeTypes.ITreeNodesType>;
  }
) => {
  console.log('treeInfoAll', treeInfo);
};
</script>

<style lang="less" module>
.wrap {
  position: relative;
  min-height: 52px;
  background-color: #fff;
  margin: 20px 0 0 100px;
  border-radius: 4px;
}
</style>

LegoTreeUtils

提供一系列 Tree 相关方法

HIERARCHY_LIST

提供 hierarchy 的枚举数组,枚举值:LegoTreeTypes.CommonHierarchyEnum

示例

import { LegoTreeUtils } from '@pluve/lego-tree-vue';

const aaa = LegoTreeUtils.HIERARCHY_LIST;

excludeNode

过滤树底层没有 primaryKey 的节点

Params

excludeNode(list: LegoTreeTypes.ITreeNodesType[], primaryKey?: string)

| 参数 | 说明 | 类型 | 是否可选 | 默认值 | | --- | --- | --- | --- | --- | | list | Tree/TreeSel