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:
- The left and right side of the shuttle box are tree structure data
- Parent and child nodes are associated and can be selected all, half, or none
- Selected data is moved to the right and not displayed on the left
- It’s essentially processing data
- 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…
- Source data box: header (checkbox), search (input), data (tree)
- Target data box: header (checkbox), search (input), data (tree)
- 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.
- Pass in parameters: mainly data source
dataSource
And the target data boxtargetKeys
Set (heretargetKeys
City ID only, not province ID) - 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
- The processing basis of data filtering
target_keys
和filterFrom
Filter the data. (1) Source data filtering out containstarget_keys
The data. (2) The target data is only retained includingtarget_keys
The 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
- 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
- Select all: Iterate to obtain all keys
- 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