Work, code more than ah, I double 叒 yi.

The list shuttle box and single tree shuttle box were implemented before. This time, the complete tree shuttle box is mainly realized. The left and right sides of the shuttle box are tree structures, and there is a linkage relationship between father and son nodes, which is still used for city selection.

The realization effect is shown in the figure below:

Main function points:

  1. The left and right side of the shuttle box are tree structure data
  2. Parent and child nodes are associated and can be selected all, half, or none
  3. Selected data is moved to the right and not displayed on the left
  4. It’s essentially processing data
  5. Used for city selection, showing data structure but actually returning city data

In the following code display, the processing on the left and right sides of the shuttle box is basically the same. Only the source data box on the left is taken as an example

The UI to complete

UI is basically imitate the style of the official website, style name is also directly borrowed from the official website, convenient and fast…

  1. Source data box: header (checkbox), search (input), data (tree)
  2. Target data box: header (checkbox), search (input), data (tree)
  3. Operation bar: Left button (button), right button (button)

The implementation code is as follows:

<template>
  <div class="tree-transfer ant-transfer ant-transfer-customize-list">
    <! -- Source data box -->
    <div class="ant-transfer-list">
      <! -- Header -->
      <div class="ant-transfer-list-header">
        <a-checkbox
          :indeterminate="from_is_indeterminate"
          v-model="from_check_all"
          @change="fromAllBoxChange"
        />
        <span class="ant-transfer-list-header-selected">
          <span
            >{{ from_check_keys.length || 0 }}/{{ from_all_keys.length }} {{
            locale.itemUnit }}</span
          >
          <span class="ant-transfer-list-header-title">{{ fromTitle }}</span>
        </span>
      </div>
      <! -- Body content -->
      <div class="ant-transfer-list-body ant-transfer-list-body-with-search">
        <! -- Search box -->
        <div v-if="filter" class="ant-transfer-list-body-search-wrapper">
          <div>
            <a-input
              v-model="filterFrom"
              :placeholder="locale.searchPlaceholder"
              class="ant-transfer-list-search"
            />
            <a class="ant-transfer-list-search-action">
              <a-icon
                type="close-circle"
                theme="filled"
                v-if="filterFrom && filterFrom.length > 0"
                @click="filterFrom = ''"
              />
              <a-icon type="search" v-else />
            </a>
          </div>
        </div>
        <! -- Tree list -->
        <div class="ant-transfer-list-body-customize-wrapper">
          <a-tree
            ref="from-tree"
            class="tt-tree from-tree"
            blockNode
            checkable
            :checked-keys="from_check_keys"
            :expanded-keys="from_expand_keys"
            :tree-data="self_from_data"
            @check="fromTreeChecked"
            @expand="fromTreeExpanded"
            :style="{ height: treeHeight + 'px' }"
          />
        </div>
      </div>
    </div>

    <! -- Action bar -->
    <div class="ant-transfer-operation">
      <a-button
        type="primary"
        @click="addToAims(true)"
        shape="circle"
        :disabled="from_disabled"
        icon="right"
      ></a-button>
      <a-button
        type="primary"
        @click="removeToSource"
        shape="circle"
        :disabled="to_disabled"
        icon="left"
      ></a-button>
    </div>

    <! -- Target data box -->
    <! -- omitted, same as source data -->
  </div>
</template>
Copy the code

The realization effect is shown in the figure below:

The data processing

The display of the left and right sides of the shuttle box is essentially to process data. Next, we will focus on the way of data processing.

  1. Pass in parameters: mainly data sourcedataSourceAnd the target data boxtargetKeysSet (heretargetKeysCity ID only, not province ID)
  2. Callback parameters:targetKeys(Select key set on the right)

Passed parameters (props)

Parameter names type Whether will pass note
dataSource Array Y The data source
targetKeys Array Y Key set of data in the right box
titles Array N Header title, default [” source list “, “target list “]
locale Object N Configuration items
filter Boolean N Whether to display the search box
replaceFields Object N Replace the corresponding field in treeData

Component internal parameters

data() {
  return {
    data_source: [...this.dataSource], / / the data source
    target_keys: [].// Key set of data in the right box
    from_is_indeterminate: false.// Whether the source data is half-selected
    from_check_all: false.// Check whether all source data are selected
    to_is_indeterminate: false.// Whether the target data is half-selected
    to_check_all: false.// Whether to select all target data
    from_disabled: true.// Whether the add button is disabled
    to_disabled: true.// Whether the remove button is disabled
    from_check_keys: [].// Source data select key array with this property associated with the shuttle button, always select all, half select state
    to_check_keys: [].// Select the key array with this property associated with the shuttle button, always select all, half select state
    from_expand_keys: [].// The source data expands the key array
    to_expand_keys: [].// Target data expands the key array
    from_all_keys: [].// All keys of source data
    to_all_keys: [].// Target data has all keys
    filterFrom: "".// Source data filtering
    filterTo: "".// Filter the target data
  };
}
Copy the code

The data processing

  1. The processing basis of data filteringtarget_keysfilterFromFilter the data. (1) Source data filtering out containstarget_keysThe data. (2) The target data is only retained includingtarget_keysThe data of
computed: {
  / / the source data
  self_from_data() {
    // Source data filtering
    let from_array = filterSourceTree(
      this.data_source,
      this.target_keys,
      this.filterFrom,
      this.replaceFields
    );

    // === is used to process all or half selection ====
    // Get all the keys of the source data
    this.from_all_keys = this.getAllKeys(from_array);
    // Get the set of all selected keys for the source data
    this.from_check_keys = this.from_check_keys.filter((key) = >
      this.from_all_keys.includes(key)
    );
    return from_array;
  },
  // Source data menu name
  fromTitle() {
    let [text] = this.titles;
    returntext; }}Copy the code
  1. Handles all, half, or unselected states
watch: {
  /* Left side status monitoring */
  from_check_keys(val) {
    if (val.length > 0) {
      // Whether the shuttle button is disabled
      this.from_disabled = false;
      // Select whether to enable
      this.from_is_indeterminate = true;

      // Total Select Whether to enable: Check whether the number of root nodes in the selected node is equal to the length of source data
      // Get the set of all province keys
      let allParentKeys = this.self_from_data.map(
        (item) = > item[this.replaceFields.key]
      );
      // Get the set of all selected keys
      let allCheck = val.filter((item) = > allParentKeys.includes(item));
      // 1. Select all if the value is equal
      if (allCheck.length == this.self_from_data.length) {
        // Turn off half-select to turn on all-select
        this.from_is_indeterminate = false;
        this.from_check_all = true;
      } else {
        // 2. Otherwise, partially select
        this.from_is_indeterminate = true;
        this.from_check_all = false; }}else {
      // 3. If the command is not selected, the command is not selected
      this.from_disabled = true;
      this.from_is_indeterminate = false;
      this.from_check_all = false; }}}Copy the code

An event definition

All events

  1. Select all: Iterate to obtain all keys
  2. Cancel all: The key is empty
/* Source data total select checkbox */
fromAllBoxChange(val) {
  if (this.self_from_data.length == 0) {
    return;
  }
  if (val.target.checked) {
    this.from_check_keys = this.getAllKeys(this.self_from_data);
  } else {
    this.from_check_keys = [];
  }
  this.$emit("left-check-change".this.from_check_all);
}
Copy the code

Get all keys for data

getAllKeys(data) {
  let result = [];
  data.forEach((item) = > {
    result.push(item[this.replaceFields.key]);
    if (item.children && item.children.length) {
      item.children.forEach((o) = > {
        result.push(o[this.replaceFields.key]); }); }});return result;
}
Copy the code

Check box event

Triggered when the check box is clicked, handled directly with the @check event of the Tree

fromTreeChecked(checkedKeys, e) {
  this.from_check_keys = checkedKeys;
}
Copy the code

Expand/collapse the event

Triggered when a node is expanded/collapsed, handled directly with the @expand event of the Tree

fromTreeExpanded(expandedKeys) {
  this.from_expand_keys = expandedKeys;
}
Copy the code

Analog data processing

Basic data

[{id: "1000".pid: "0".value: Hubei Province.label: Hubei Province.children: [{id: "1001".pid: "1000".label: "Wuhan" },
      { id: "1020".pid: "1000".label: "Xianning" },
      { id: "1022".pid: "1000".label: "Xiaogan" },
      { id: "1034".pid: "1000".label: "Xiangyang" },
      { id: "1003".pid: "1000".label: "Yichang"},],}, {id: "1200".pid: "0".value: Jiangsu Province.label: Jiangsu Province.children: [{id: "1201".pid: "1200".label: "Nanjing" },
      { id: "1202".pid: "1200".label: "Suzhou" },
      { id: "1204".pid: "1200".label: "Yangzhou"},],},];Copy the code

Source data processing

The selected data will not appear in the source data box, just filter it out

const filterSourceTree = (
  tree = [],
  targetKeys = [],
  keyword = "",
  replaceFields
) = > {
  if(! tree.length) {return [];
  }
  const result = [];
  for (let item of tree) {
    if (item[replaceFields.title].includes(keyword)) {
      if (item.children && item.children.length) {
        letele = { ... item,children: []};for (let o of item.children) {
          if (targetKeys.includes(o[replaceFields.key])) continue;
          ele.children.push(o);
        }
        if(ele.children.length) { result.push(ele); }}}else {
      if (item.children && item.children.length) {
        letnode = { ... item,children: []};for (let o of item.children) {
          if (
            !(
              !targetKeys.includes(o[replaceFields.key]) &&
              o[replaceFields.title].includes(keyword)
            )
          )
            continue;
          node.children.push(o);
        }
        if(node.children.length) { result.push(node); }}}}return result;
};

let leftSource = filterSourceTree(
  this.provinceData,
  // Wuhan, Xianning, Nanjing
  ["1001"."1020"."1201"]."".this.replaceFields
);
console.log(leftSource);
Copy the code

Target data processing

Data that has been selected will appear in the target data box

const filterTargetTree = (
  tree = [],
  targetKeys = [],
  keyword = "",
  replaceFields
) = > {
  if(! tree.length) {return [];
  }
  const result = [];
  for (let item of tree) {
    if (item[replaceFields.title].includes(keyword)) {
      if (item.children && item.children.length) {
        letele = { ... item,children: []};for (let o of item.children) {
          if(! targetKeys.includes(o[replaceFields.key]))continue;
          ele.children.push(o);
        }
        if(ele.children.length) { result.push(ele); }}}else {
      if (item.children && item.children.length) {
        letnode = { ... item,children: []};for (let o of item.children) {
          if (
            !(
              targetKeys.includes(o[replaceFields.key]) &&
              o[replaceFields.title].includes(keyword)
            )
          )
            continue;
          node.children.push(o);
        }
        if(node.children.length) { result.push(node); }}}}return result;
};

// Target data processing
let rightSource = filterTargetTree(
  this.provinceData,
  ["1001"."1020"."1201"]."".this.replaceFields
);
console.log(rightSource);
this.provinceData = rightSource;
Copy the code

Function transformation

The previous functions filterSourceTree and filterTargetTree filter keywords and target data at the same time, which are relatively rough, and only targeted at the secondary tree structure at that time, so the scalability is not strong. Then it was transformed and optimized.

Specific functions can be seen: summary of common operations on front-end tree structure data

Source data processing

computed: {
  / / the source data
  self_from_data() {
    // Select data filtering
    let from_array = filterSourceTreeFn(this.data_source, this.target_keys);
    // Keyword filtering
    if (this.filterFrom) {
      from_array = filterKeywordTreeFn(from_array, this.filterFrom);
    }
    returnfrom_array; }},Copy the code

Target data processing

computed: {
  // Target data
  self_to_data() {
    // Select Data retention
    let to_array = filterTargetTreeFn(this.data_source, this.target_keys);
    // Keyword filtering
    if (this.filterTo) {
      to_array = filterKeywordTreeFn(to_array, this.filterTo);
    }
    returnto_array; }},Copy the code