forked from github/dataease
Merge pull request #2313 from dataease/pr@dev@feat_tree_select
feat: 过滤条件增加下拉树
This commit is contained in:
commit
f5241528ff
@ -54,7 +54,6 @@ public class ShiroServiceImpl implements ShiroService {
|
||||
// 验证链接
|
||||
filterChainDefinitionMap.put("/api/link/validate**", ANON);
|
||||
filterChainDefinitionMap.put("/api/map/areaEntitys/**", ANON);
|
||||
filterChainDefinitionMap.put("/dataset/field/fieldValues/**", ANON);
|
||||
filterChainDefinitionMap.put("/linkJump/queryPanelJumpInfo/**", ANON);
|
||||
filterChainDefinitionMap.put("/linkJump/queryTargetPanelJumpInfo", ANON);
|
||||
|
||||
@ -98,6 +97,7 @@ public class ShiroServiceImpl implements ShiroService {
|
||||
filterChainDefinitionMap.put("/api/link/viewDetail/**", "link");
|
||||
filterChainDefinitionMap.put("/panel/group/exportDetails", ANON);
|
||||
filterChainDefinitionMap.put("/dataset/field/linkMultFieldValues", "link");
|
||||
filterChainDefinitionMap.put("/dataset/field/linkMappingFieldValues", "link");
|
||||
|
||||
filterChainDefinitionMap.put("/**", "authc");
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
package io.dataease.commons.model;
|
||||
|
||||
import io.dataease.plugins.common.model.ITreeBase;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BaseTreeNode implements ITreeBase<BaseTreeNode> {
|
||||
|
||||
private String id;
|
||||
|
||||
private String pid;
|
||||
|
||||
private String text;
|
||||
|
||||
private String nodeType;
|
||||
|
||||
private List<BaseTreeNode> children;
|
||||
|
||||
public BaseTreeNode(String id, String pid, String text, String nodeType) {
|
||||
this.id = id;
|
||||
this.pid = pid;
|
||||
this.text = text;
|
||||
this.nodeType = nodeType;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package io.dataease.commons.utils;
|
||||
|
||||
import io.dataease.plugins.common.model.ITreeBase;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@ -14,6 +15,9 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class TreeUtils{
|
||||
|
||||
public final static String DEFAULT_ROOT = "root";
|
||||
public final static String SEPARATOR = "-de-";
|
||||
|
||||
/**
|
||||
* Description: rootPid 是根节点PID
|
||||
*/
|
||||
@ -53,4 +57,41 @@ public class TreeUtils{
|
||||
return mergeTree(tree,"0");
|
||||
}
|
||||
|
||||
|
||||
public static<T extends ITreeBase> List<T> mergeDuplicateTree(List<T> tree, String ... rootPid) {
|
||||
Assert.notNull(rootPid, "Root Pid cannot be null");
|
||||
if(CollectionUtils.isEmpty(tree)){
|
||||
return null;
|
||||
}
|
||||
List<T> result = new ArrayList<>();
|
||||
// 构建id-节点map映射
|
||||
Map<String, T> treePidMap = tree.stream().collect(Collectors.toMap(node -> node.getNodeType(), t -> t));
|
||||
tree.stream().filter(item -> StringUtils.isNotBlank(item.getId())).forEach(node -> {
|
||||
|
||||
String nodeType = node.getNodeType();
|
||||
String[] links = nodeType.split(SEPARATOR);
|
||||
int length = links.length;
|
||||
int level = Integer.parseInt(links[length - 1]);
|
||||
// 判断根节点
|
||||
if (Arrays.asList(rootPid).contains(node.getPid()) && 0 == level) {
|
||||
result.add(node);
|
||||
} else {
|
||||
//找到父元素
|
||||
String[] pLinks = new String[level];
|
||||
System.arraycopy(links, 0, pLinks, 0, level);
|
||||
String parentType = Arrays.stream(pLinks).collect(Collectors.joining(SEPARATOR)) + TreeUtils.SEPARATOR + (level-1);
|
||||
T parentNode = treePidMap.get(parentType);
|
||||
if(parentNode==null){
|
||||
// 可能出现 rootPid 更高的节点 这个操作相当于截断
|
||||
return;
|
||||
}
|
||||
if (parentNode.getChildren() == null) {
|
||||
parentNode.setChildren(new ArrayList());
|
||||
}
|
||||
parentNode.getChildren().add(node);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -177,6 +177,23 @@ public class DataSetTableFieldController {
|
||||
return list;
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping("linkMappingFieldValues")
|
||||
public List<Object> linkMappingFieldValues(@RequestBody MultFieldValuesRequest multFieldValuesRequest) throws Exception {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String linkToken = request.getHeader(F2CLinkFilter.LINK_TOKEN_KEY);
|
||||
DecodedJWT jwt = JWT.decode(linkToken);
|
||||
Long userId = jwt.getClaim("userId").asLong();
|
||||
multFieldValuesRequest.setUserId(userId);
|
||||
return dataSetFieldService.fieldValues(multFieldValuesRequest.getFieldIds(), multFieldValuesRequest.getUserId(), true, true);
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping("mappingFieldValues")
|
||||
public List<Object> mappingFieldValues(@RequestBody MultFieldValuesRequest multFieldValuesRequest) throws Exception {
|
||||
return dataSetFieldService.fieldValues(multFieldValuesRequest.getFieldIds(), multFieldValuesRequest.getUserId(), true, true);
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping("multFieldValuesForPermissions")
|
||||
public List<Object> multFieldValuesForPermissions(@RequestBody MultFieldValuesRequest multFieldValuesRequest) throws Exception {
|
||||
|
@ -6,4 +6,6 @@ import java.util.List;
|
||||
public interface DataSetFieldService {
|
||||
|
||||
List<Object> fieldValues(String fieldId, Long userId, Boolean userPermissions) throws Exception;
|
||||
|
||||
List<Object> fieldValues(List<String> fieldIds, Long userId, Boolean userPermissions, Boolean needMapping) throws Exception;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.dataease.service.dataset.impl.direct;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.dataease.commons.model.BaseTreeNode;
|
||||
import io.dataease.commons.utils.TreeUtils;
|
||||
import io.dataease.plugins.common.base.domain.DatasetTable;
|
||||
import io.dataease.plugins.common.base.domain.DatasetTableField;
|
||||
import io.dataease.plugins.common.base.domain.Datasource;
|
||||
@ -22,9 +24,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@ -45,6 +45,14 @@ public class DirectFieldService implements DataSetFieldService {
|
||||
|
||||
@Override
|
||||
public List<Object> fieldValues(String fieldId, Long userId, Boolean userPermissions) throws Exception {
|
||||
List<String> filedIds = new ArrayList<>();
|
||||
filedIds.add(fieldId);
|
||||
return fieldValues(filedIds, userId, userPermissions, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> fieldValues(List<String> fieldIds, Long userId, Boolean userPermissions, Boolean needMapping) throws Exception {
|
||||
String fieldId = fieldIds.get(0);
|
||||
DatasetTableField field = dataSetTableFieldsService.selectByPrimaryKey(fieldId);
|
||||
if (field == null || StringUtils.isEmpty(field.getTableId())) return null;
|
||||
|
||||
@ -54,15 +62,23 @@ public class DirectFieldService implements DataSetFieldService {
|
||||
DatasetTableField datasetTableField = DatasetTableField.builder().tableId(field.getTableId()).checked(Boolean.TRUE).build();
|
||||
List<DatasetTableField> fields = dataSetTableFieldsService.list(datasetTableField);
|
||||
|
||||
List<DatasetTableField> permissionFields = fields;
|
||||
List<ChartFieldCustomFilterDTO> customFilter = new ArrayList<>();
|
||||
if(userPermissions){
|
||||
//列权限
|
||||
List<String> desensitizationList = new ArrayList<>();
|
||||
fields = permissionService.filterColumnPermissons(fields, desensitizationList, datasetTable.getId(), userId);
|
||||
//禁用的
|
||||
if(!fields.stream().map(DatasetTableField::getId).collect(Collectors.toList()).contains(fieldId)){
|
||||
|
||||
|
||||
permissionFields = fields.stream().filter(node -> fieldIds.stream().anyMatch(item -> StringUtils.equals(node.getId(), item))).collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isEmpty(permissionFields)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
//禁用的
|
||||
/*if(!fields.stream().map(DatasetTableField::getId).collect(Collectors.toList()).contains(fieldId)){
|
||||
return new ArrayList<>();
|
||||
}*/
|
||||
if (CollectionUtils.isNotEmpty(desensitizationList) && desensitizationList.contains(field.getDataeaseName())) {
|
||||
List<Object> results = new ArrayList<>();
|
||||
results.add(ColumnPermissionConstants.Desensitization_desc);
|
||||
@ -87,18 +103,18 @@ public class DirectFieldService implements DataSetFieldService {
|
||||
QueryProvider qp = ProviderFactory.getQueryProvider(ds.getType());
|
||||
if (StringUtils.equalsIgnoreCase(datasetTable.getType(), "db")) {
|
||||
datasourceRequest.setTable(dataTableInfoDTO.getTable());
|
||||
datasourceRequest.setQuery(qp.createQuerySQL(dataTableInfoDTO.getTable(), Collections.singletonList(field), true, ds, customFilter));
|
||||
datasourceRequest.setQuery(qp.createQuerySQL(dataTableInfoDTO.getTable(), permissionFields, true, ds, customFilter));
|
||||
} else if (StringUtils.equalsIgnoreCase(datasetTable.getType(), "sql")) {
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(dataTableInfoDTO.getSql(), Collections.singletonList(field), true, customFilter));
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(dataTableInfoDTO.getSql(), permissionFields, true, customFilter));
|
||||
} else if (StringUtils.equalsIgnoreCase(datasetTable.getType(), "custom")) {
|
||||
DataTableInfoDTO dt = new Gson().fromJson(datasetTable.getInfo(), DataTableInfoDTO.class);
|
||||
List<DataSetTableUnionDTO> listUnion = dataSetTableUnionService.listByTableId(dt.getList().get(0).getTableId());
|
||||
String sql = dataSetTableService.getCustomSQLDatasource(dt, listUnion, ds);
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(sql, Collections.singletonList(field), true, customFilter));
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(sql, permissionFields, true, customFilter));
|
||||
} else if (StringUtils.equalsIgnoreCase(datasetTable.getType(), "union")) {
|
||||
DataTableInfoDTO dt = new Gson().fromJson(datasetTable.getInfo(), DataTableInfoDTO.class);
|
||||
String sql = (String) dataSetTableService.getUnionSQLDatasource(dt, ds).get("sql");
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(sql, Collections.singletonList(field), true, customFilter));
|
||||
datasourceRequest.setQuery(qp.createQuerySQLAsTmp(sql, permissionFields, true, customFilter));
|
||||
}
|
||||
} else if (datasetTable.getMode() == 1) {// 抽取
|
||||
// 连接doris,构建doris数据源查询
|
||||
@ -109,11 +125,39 @@ public class DirectFieldService implements DataSetFieldService {
|
||||
String tableName = "ds_" + datasetTable.getId().replaceAll("-", "_");
|
||||
datasourceRequest.setTable(tableName);
|
||||
QueryProvider qp = ProviderFactory.getQueryProvider(ds.getType());
|
||||
datasourceRequest.setQuery(qp.createQuerySQL(tableName, Collections.singletonList(field), true, null, customFilter));
|
||||
datasourceRequest.setQuery(qp.createQuerySQL(tableName, permissionFields, true, null, customFilter));
|
||||
}
|
||||
|
||||
List<String[]> rows = datasourceProvider.getData(datasourceRequest);
|
||||
List<Object> results = rows.stream().map(row -> row[0]).distinct().collect(Collectors.toList());
|
||||
return results;
|
||||
if (!needMapping) {
|
||||
List<Object> results = rows.stream().map(row -> row[0]).distinct().collect(Collectors.toList());
|
||||
return results;
|
||||
}
|
||||
Set<String> pkSet = new HashSet<>();
|
||||
|
||||
List<BaseTreeNode> treeNodes = rows.stream().map(row -> buildTreeNode(row, pkSet)).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
List tree = TreeUtils.mergeDuplicateTree(treeNodes, TreeUtils.DEFAULT_ROOT);
|
||||
return tree;
|
||||
|
||||
}
|
||||
|
||||
private List<BaseTreeNode> buildTreeNode(String [] row, Set<String> pkSet) {
|
||||
List<BaseTreeNode> nodes = new ArrayList<>();
|
||||
List<String> parentPkList = new ArrayList<>();
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
String text = row[i];
|
||||
|
||||
parentPkList.add(text);
|
||||
String val = parentPkList.stream().collect(Collectors.joining(TreeUtils.SEPARATOR));
|
||||
String parentVal = i == 0 ? TreeUtils.DEFAULT_ROOT : row[i - 1];
|
||||
String pk = parentPkList.stream().collect(Collectors.joining(TreeUtils.SEPARATOR));
|
||||
if (pkSet.contains(pk)) continue;
|
||||
pkSet.add(pk);
|
||||
BaseTreeNode node = new BaseTreeNode(val, parentVal, text, pk + TreeUtils.SEPARATOR + i);
|
||||
nodes.add(node);
|
||||
}
|
||||
return nodes;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -146,11 +146,21 @@ export function post(url, data, showLoading = true, timeout = 60000) {
|
||||
})
|
||||
}
|
||||
|
||||
export function fieldValues(fieldId) {
|
||||
export function mappingFieldValues(data) {
|
||||
return request({
|
||||
url: '/dataset/field/fieldValues/' + fieldId,
|
||||
url: '/dataset/field/mappingFieldValues',
|
||||
method: 'post',
|
||||
loading: true
|
||||
loading: true,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function linkMappingFieldValues(data) {
|
||||
return request({
|
||||
url: '/dataset/field/linkMappingFieldValues',
|
||||
method: 'post',
|
||||
loading: true,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
|
37
frontend/src/components/ElTreeSelect/dom.js
Normal file
37
frontend/src/components/ElTreeSelect/dom.js
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* @moduleName:
|
||||
* @Author: dawdler
|
||||
* @LastModifiedBy: dawdler
|
||||
* @Date: 2019-03-22 14:47:35
|
||||
* @LastEditTime: 2019-03-22 16:31:38
|
||||
*/
|
||||
export const on = (function() {
|
||||
if (document.addEventListener) {
|
||||
return function(element, event, handler) {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return function(element, event, handler) {
|
||||
if (element && event && handler) {
|
||||
element.attachEvent('on' + event, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
export const off = (function() {
|
||||
if (document.removeEventListener) {
|
||||
return function(element, event, handler) {
|
||||
if (element && event) {
|
||||
element.removeEventListener(event, handler, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return function(element, event, handler) {
|
||||
if (element && event) {
|
||||
element.detachEvent('on' + event, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
594
frontend/src/components/ElTreeSelect/index.vue
Normal file
594
frontend/src/components/ElTreeSelect/index.vue
Normal file
@ -0,0 +1,594 @@
|
||||
<!--
|
||||
* @moduleName: 下拉树组件
|
||||
* @Author: dawdler
|
||||
* @Date: 2018-12-19 14:03:03
|
||||
* @LastModifiedBy: dawdler
|
||||
* @LastEditTime: 2020-12-26 14:51:20
|
||||
-->
|
||||
<template>
|
||||
<div class="el-tree-select" :class="selectClass">
|
||||
<!-- 下拉文本 -->
|
||||
<el-select
|
||||
:id="'el-tree-select-' + guid"
|
||||
ref="select"
|
||||
v-model="labels"
|
||||
v-popover:popover
|
||||
:style="styles"
|
||||
class="el-tree-select-input"
|
||||
:disabled="disabled"
|
||||
popper-class="select-option"
|
||||
v-bind="selectParams"
|
||||
:popper-append-to-body="false"
|
||||
:filterable="false"
|
||||
:multiple="selectParams.multiple"
|
||||
:title="labels"
|
||||
@remove-tag="_selectRemoveTag"
|
||||
@clear="_selectClearFun"
|
||||
@focus="_popoverShowFun"
|
||||
/>
|
||||
<!-- 弹出框 -->
|
||||
<el-popover ref="popover" v-model="visible" :placement="placement" :transition="transition" :popper-class="popperClass" :width="width" trigger="click">
|
||||
<!-- 是否显示搜索框 -->
|
||||
<el-input v-if="treeParams.filterable" v-model="keywords" size="mini" class="input-with-select mb10" @change="_searchFun">
|
||||
<el-button slot="append" icon="el-icon-search" />
|
||||
</el-input>
|
||||
<el-scrollbar tag="div" wrap-class="el-select-dropdown__wrap" view-class="el-select-dropdown__list" class="is-empty">
|
||||
<!-- 树列表 -->
|
||||
<el-tree
|
||||
v-show="data.length > 0"
|
||||
ref="tree"
|
||||
v-bind="treeParams"
|
||||
:data="data"
|
||||
:node-key="propsValue"
|
||||
:draggable="false"
|
||||
:current-node-key="ids.length > 0 ? ids[0] : ''"
|
||||
:show-checkbox="selectParams.multiple"
|
||||
:filter-node-method="filterNodeMethod ? filterNodeMethod : _filterFun"
|
||||
:render-content="treeRenderFun"
|
||||
@node-click="_treeNodeClickFun"
|
||||
@check="_treeCheckFun"
|
||||
/>
|
||||
<!-- 暂无数据 -->
|
||||
<div v-if="data.length === 0" class="no-data">暂无数据</div>
|
||||
</el-scrollbar>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { on, off } from './dom'
|
||||
import { each, guid } from './utils'
|
||||
// @group api
|
||||
export default {
|
||||
name: 'ElTreeSelect',
|
||||
components: {},
|
||||
props: {
|
||||
// v-model,存储的是treeParams.data里面的id
|
||||
value: {
|
||||
// `String` / `Array` / `Number`
|
||||
type: [String, Array, Number],
|
||||
// `''`
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// el-select样式
|
||||
styles: {
|
||||
type: Object,
|
||||
// {}
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 下拉框 挂类
|
||||
selectClass: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// popover 挂类
|
||||
popoverClass: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// 是否禁用文本框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
// false
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 弹出框位置
|
||||
placement: {
|
||||
type: String,
|
||||
// bottom
|
||||
default() {
|
||||
return 'bottom'
|
||||
}
|
||||
},
|
||||
// 弹出框过渡动画
|
||||
transition: {
|
||||
type: String,
|
||||
// el-zoom-in-top
|
||||
default() {
|
||||
return 'el-zoom-in-top'
|
||||
}
|
||||
},
|
||||
// 树渲染方法,具体参考el-tree Function(h, { node, data, store }) {}
|
||||
treeRenderFun: Function,
|
||||
// 搜索过滤方法,具体参考el-tree Function(h, { value, data, node }) {}
|
||||
filterNodeMethod: Function,
|
||||
/*
|
||||
文本框参数,几乎支持el-select所有的API<br>
|
||||
取消参数:<br>
|
||||
设定下拉框的弹出框隐藏:<br>
|
||||
`:popper-append-to-body="false"` <br>
|
||||
搜索从弹出框里面执行: <br>
|
||||
`filterable="false"`
|
||||
*/
|
||||
selectParams: {
|
||||
type: Object,
|
||||
/*
|
||||
Object默认参数:<br><br>
|
||||
是否可以清空选项:<br>
|
||||
`clearable: true,`<br><br>
|
||||
是否禁用:<br>
|
||||
`disabled: false,`<br><br>
|
||||
搜索框placeholder文字:<br>
|
||||
`placeholder: '请选择',`<br><br>
|
||||
*/
|
||||
default() {
|
||||
return {
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
placeholder: '请选择'
|
||||
}
|
||||
}
|
||||
},
|
||||
/*
|
||||
下拉树参数,几乎支持el-tree所有的API<br>
|
||||
取消参数:<br>
|
||||
`:show-checkbox="selectParams.multiple"`<br>
|
||||
使用下拉框参数multiple判断是否对树进行多选<br>
|
||||
取消对el-tree的人为传参show-checkbox<br>
|
||||
`:node-key="propsValue"` 自动获取treeParams.props.value<br>
|
||||
`:draggable="false"` 屏蔽拖动
|
||||
*/
|
||||
treeParams: {
|
||||
type: Object,
|
||||
/*
|
||||
Object默认参数:<br><br>
|
||||
在有子级的情况下是否点击父级关闭弹出框,false 只能点击子级关闭弹出框:<br><br>
|
||||
`clickParent: false`<br><br>
|
||||
是否显示搜索框:<br><br>
|
||||
`filterable: false`<br><br>
|
||||
是否只是叶子节点:<br><br>
|
||||
`leafOnly: false`<br><br>
|
||||
是否包含半选节点:<br><br>
|
||||
`includeHalfChecked: false`<br><br>
|
||||
下拉树的数据:<br><br>
|
||||
`data:[]`<br><br>
|
||||
下拉树的props:<br><br>
|
||||
`props: {`<br>
|
||||
`children: 'children',`<br>
|
||||
`label: 'name',`<br>
|
||||
`value: 'flowId',`<br>
|
||||
`disabled: 'disabled'`<br>
|
||||
`}`
|
||||
*/
|
||||
default() {
|
||||
return {
|
||||
clickParent: false,
|
||||
filterable: false,
|
||||
leafOnly: false,
|
||||
includeHalfChecked: false,
|
||||
data: [],
|
||||
showParent: false,
|
||||
props: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
code: 'code',
|
||||
value: 'flowId',
|
||||
disabled: 'disabled'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
guid: guid(),
|
||||
propsValue: 'flowId',
|
||||
propsLabel: 'name',
|
||||
propsCode: null, // 可能有空的情况
|
||||
propsDisabled: 'disabled',
|
||||
propsChildren: 'children',
|
||||
leafOnly: false,
|
||||
includeHalfChecked: false,
|
||||
data: [],
|
||||
keywords: '',
|
||||
labels: '', // 存储名称,用于下拉框显示内容
|
||||
ids: [], // 存储id
|
||||
visible: false, // popover v-model
|
||||
width: 150,
|
||||
showParent: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
popperClass() {
|
||||
const _c = 'el-tree-select-popper ' + this.popoverClass
|
||||
return this.disabled ? _c + ' disabled ' : _c
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
ids: function(val) {
|
||||
if (val !== undefined) {
|
||||
this.$nextTick(() => {
|
||||
this._setSelectNodeFun(val)
|
||||
})
|
||||
}
|
||||
},
|
||||
value: function(val) {
|
||||
if (this.ids !== val) {
|
||||
this._setMultipleFun()
|
||||
if (this.selectParams.multiple) {
|
||||
this.ids = [...val]
|
||||
} else {
|
||||
this.ids = val === '' ? [] : [val]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const { props, data, leafOnly, includeHalfChecked, showParent } = this.treeParams
|
||||
this._setMultipleFun()
|
||||
this.propsValue = props.value
|
||||
this.propsLabel = props.label
|
||||
this.propsCode = props.code || null // 可能为空
|
||||
this.propsDisabled = props.disabled
|
||||
this.propsChildren = props.children
|
||||
this.leafOnly = leafOnly
|
||||
this.includeHalfChecked = includeHalfChecked
|
||||
this.data = data.length > 0 ? [...data] : []
|
||||
if (this.selectParams.multiple) {
|
||||
this.labels = []
|
||||
this.ids = this.value
|
||||
} else {
|
||||
this.labels = ''
|
||||
this.ids = this.value instanceof Array ? this.value : [this.value]
|
||||
}
|
||||
this.showParent = showParent
|
||||
},
|
||||
mounted() {
|
||||
this._updateH()
|
||||
this.$nextTick(() => {
|
||||
on(document, 'mouseup', this._popoverHideFun)
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
off(document, 'mouseup', this._popoverHideFun)
|
||||
},
|
||||
methods: {
|
||||
// 根据类型判断单选,多选
|
||||
_setMultipleFun() {
|
||||
let multiple = false
|
||||
if (this.value instanceof Array) {
|
||||
multiple = true
|
||||
}
|
||||
this.$set(this.selectParams, 'multiple', multiple)
|
||||
},
|
||||
// 输入文本框输入内容抛出
|
||||
_searchFun() {
|
||||
/*
|
||||
对外抛出搜索方法,自行判断是走后台查询,还是前端过滤<br>
|
||||
前端过滤:this.$refs.treeSelect.$refs.tree.filter(value);<br>
|
||||
后台查询:this.$refs.treeSelect.treeDataUpdateFun(data);
|
||||
*/
|
||||
this.$emit('searchFun', this.keywords)
|
||||
},
|
||||
// 根据id筛选当前树名称,以及选中树列表
|
||||
_setSelectNodeFun(ids) {
|
||||
const el = this.$refs.tree
|
||||
if (!el) {
|
||||
throw new Error('找不到tree dom')
|
||||
}
|
||||
const { multiple } = this.selectParams
|
||||
// 长度为0,清空选择
|
||||
if (ids.length === 0 || this.data.length === 0) {
|
||||
this.labels = multiple ? [] : ''
|
||||
if (multiple) {
|
||||
el.setCheckedKeys([])
|
||||
} else {
|
||||
el.setCurrentKey(null)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (multiple) {
|
||||
// element-ui bug. 如果是父子节点全选 el.setCheckedKeys([非全量id]);之后el.getCheckedNodes()还是全量
|
||||
el.getCheckedNodes(this.leafOnly, this.includeHalfChecked).forEach(item => {
|
||||
el.setChecked(item, false)
|
||||
})
|
||||
ids.forEach(id => {
|
||||
el.setChecked(id, true)
|
||||
})
|
||||
const nodes = el.getCheckedNodes(this.leafOnly, this.includeHalfChecked)
|
||||
if (!this.showParent) {
|
||||
if (this.propsCode) {
|
||||
// 如果有code labels=code(name)
|
||||
this.labels = nodes.map(item => (item[this.propsCode] ? item[this.propsLabel] + '(' + item[this.propsCode] + ')' : item[this.propsLabel])) || []
|
||||
} else {
|
||||
this.labels = nodes.map(item => item[this.propsLabel]) || []
|
||||
}
|
||||
} else {
|
||||
this.labels = nodes.map(item => this.cascadeLabels(item)) || []
|
||||
}
|
||||
} else {
|
||||
el.setCurrentKey(ids[0])
|
||||
const node = el.getCurrentNode()
|
||||
if (node) {
|
||||
if (!this.showParent) {
|
||||
if (this.propsCode) {
|
||||
// 如果有code labels=code(name)
|
||||
this.labels = node[this.propsCode] ? node[this.propsLabel] + '(' + node[this.propsCode] + ')' : node[this.propsLabel]
|
||||
} else {
|
||||
this.labels = node[this.propsLabel]
|
||||
}
|
||||
} else {
|
||||
this.labels = this.cascadeLabels(node)
|
||||
}
|
||||
} else {
|
||||
this.labels = ''
|
||||
}
|
||||
}
|
||||
this._updatePopoverLocationFun()
|
||||
},
|
||||
|
||||
parentNodes(node) {
|
||||
const results = []
|
||||
let currentNode = node
|
||||
while (currentNode && currentNode.data && !(currentNode.data instanceof Array)) {
|
||||
results.push(currentNode)
|
||||
currentNode = currentNode.parent
|
||||
}
|
||||
return results
|
||||
},
|
||||
|
||||
cascadeLabels(data) {
|
||||
const cNode = this.$refs.tree.getNode(data)
|
||||
const linkedNodes = this.parentNodes(cNode)
|
||||
const labels = linkedNodes.map(item => item.data[this.propsLabel]).reverse().join(':')
|
||||
return labels
|
||||
},
|
||||
// 更新popover位置
|
||||
_updatePopoverLocationFun() {
|
||||
// dom高度还没有更新,做一个延迟
|
||||
setTimeout(() => {
|
||||
this.$refs.popover.updatePopper()
|
||||
}, 50)
|
||||
},
|
||||
// 获取MouseEvent.path 针对浏览器兼容性兼容ie11,edge,chrome,firefox,safari
|
||||
_getEventPath(evt) {
|
||||
const path = (evt.composedPath && evt.composedPath()) || evt.path
|
||||
const target = evt.target
|
||||
if (path != null) {
|
||||
return path.indexOf(window) < 0 ? path.concat(window) : path
|
||||
}
|
||||
if (target === window) {
|
||||
return [window]
|
||||
}
|
||||
function getParents(node, memo) {
|
||||
memo = memo || []
|
||||
const parentNode = node.parentNode
|
||||
if (!parentNode) {
|
||||
return memo
|
||||
} else {
|
||||
return getParents(parentNode, memo.concat(parentNode))
|
||||
}
|
||||
}
|
||||
return [target].concat(getParents(target), window)
|
||||
},
|
||||
// 树过滤
|
||||
_filterFun(value, data, node) {
|
||||
if (!value) return true
|
||||
return data[this.propsLabel].indexOf(value) !== -1
|
||||
},
|
||||
// 树点击
|
||||
_treeNodeClickFun(data, node, vm) {
|
||||
const { multiple } = this.selectParams
|
||||
const { clickParent } = this.treeParams
|
||||
const checkStrictly = this.treeParams['check-strictly']
|
||||
const { propsValue, propsChildren, propsDisabled } = this
|
||||
const children = data[propsChildren] || []
|
||||
if (data[propsDisabled]) {
|
||||
// 禁用
|
||||
return
|
||||
}
|
||||
if (node.checked) {
|
||||
const value = data[propsValue]
|
||||
this.ids = this.ids.filter(id => id !== value)
|
||||
if (!checkStrictly && children.length) {
|
||||
children.forEach(item => {
|
||||
this.ids = this.ids.filter(id => id !== item[propsValue])
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (!multiple) {
|
||||
// 多选,不关闭,单选,判断是否允许点击父级关闭弹出框
|
||||
if (!clickParent) {
|
||||
// 如果不允许点击父级,自身为末级,允许点击之后关闭
|
||||
if (children.length === 0) {
|
||||
this.ids = [data[propsValue]]
|
||||
this.visible = false
|
||||
} else {
|
||||
// 不允许父级,阻止继续派发
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
this.ids = [data[propsValue]]
|
||||
this.visible = false
|
||||
}
|
||||
} else {
|
||||
if (!clickParent && children.length === 0) {
|
||||
// 如果不能点击父级
|
||||
this.ids.push(data[propsValue])
|
||||
} else if (clickParent) {
|
||||
// 允许点击父级
|
||||
this.ids.push(data[propsValue])
|
||||
// 如果父子关联,将子节点push进勾选项
|
||||
if (!checkStrictly && children.length) {
|
||||
children.forEach(item => {
|
||||
this.ids.push(item[propsValue])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._emitFun()
|
||||
/*
|
||||
点击节点,对外抛出 `data, node, vm`<br>
|
||||
`data:` 当前点击的节点数据<br>
|
||||
`node:` 当前点击的node<br>
|
||||
`vm:` 当前组件的vm
|
||||
*/
|
||||
this.$emit('node-click', data, node, vm)
|
||||
},
|
||||
// 树勾选
|
||||
_treeCheckFun(data, node, vm) {
|
||||
this.ids = []
|
||||
const { propsValue } = this
|
||||
node.checkedNodes.forEach(item => {
|
||||
this.ids.push(item[propsValue])
|
||||
})
|
||||
/*
|
||||
点击复选框,对外抛出 `data, node, vm`<br>
|
||||
`data:` 当前点击的节点数据<br>
|
||||
`node:` 当前点击的node<br>
|
||||
`vm:` 当前组件的vm
|
||||
*/
|
||||
this.$emit('check', data, node, vm)
|
||||
this._emitFun()
|
||||
},
|
||||
// 下拉框移除tag时触发
|
||||
_selectRemoveTag(tag) {
|
||||
const { data, propsValue, propsLabel, propsChildren } = this
|
||||
each(
|
||||
data,
|
||||
item => {
|
||||
const labels = this.showParent ? this.cascadeLabels(item) : item[propsLabel]
|
||||
if (labels === tag) {
|
||||
const value = item[propsValue]
|
||||
this.ids = this.ids.filter(id => id !== value)
|
||||
}
|
||||
},
|
||||
propsChildren
|
||||
)
|
||||
this.$refs.tree.setCheckedKeys(this.ids)
|
||||
this.$emit('removeTag', this.ids, tag)
|
||||
this._emitFun()
|
||||
},
|
||||
// 下拉框清空数据
|
||||
_selectClearFun() {
|
||||
this.ids = []
|
||||
const { multiple } = this.selectParams
|
||||
// 下拉框清空,对外抛出``this.$emit('input', multiple ? [] : '');`
|
||||
this.$emit('input', multiple ? [] : '')
|
||||
// 下拉框清空,对外抛出``this.$emit('select-clear');`
|
||||
this.$emit('select-clear')
|
||||
this._updatePopoverLocationFun()
|
||||
},
|
||||
// 判断类型,抛出当前选中id
|
||||
_emitFun() {
|
||||
const { multiple } = this.selectParams
|
||||
this.$emit('input', multiple ? this.ids : this.ids.length > 0 ? this.ids[0] : '')
|
||||
this._updatePopoverLocationFun()
|
||||
},
|
||||
// 更新宽度
|
||||
_updateH() {
|
||||
this.$nextTick(() => {
|
||||
this.width = this.$refs.select.$el.getBoundingClientRect().width
|
||||
})
|
||||
},
|
||||
// 显示弹出框的时候容错,查看是否和el宽度一致
|
||||
_popoverShowFun(val) {
|
||||
this._updateH()
|
||||
},
|
||||
// 判断是否隐藏弹出框
|
||||
_popoverHideFun(e) {
|
||||
const path = this._getEventPath(e)
|
||||
const isInside = path.some(list => {
|
||||
// 鼠标在弹出框内部,阻止隐藏弹出框
|
||||
return list.className && typeof list.className === 'string' && list.className.indexOf('el-tree-select') !== -1
|
||||
})
|
||||
if (!isInside) {
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @vuese
|
||||
* 树列表更新数据
|
||||
* @arg Array
|
||||
*/
|
||||
treeDataUpdateFun(data) {
|
||||
this.data = data
|
||||
// 数据更新完成之后,判断是否回显内容
|
||||
if (data.length > 0) {
|
||||
setTimeout(() => {
|
||||
this._setSelectNodeFun(this.ids)
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @vuese
|
||||
* 本地过滤方法
|
||||
* @arg String
|
||||
*/
|
||||
filterFun(val) {
|
||||
this.$refs.tree.filter(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.el-tree-select .select-option {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[aria-disabled='true'] > .el-tree-node__content {
|
||||
color: inherit !important;
|
||||
background: transparent !important;
|
||||
cursor: no-drop !important;
|
||||
}
|
||||
|
||||
.el-tree-select-popper {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
.el-tree-select-popper.disabled {
|
||||
display: none !important;
|
||||
}
|
||||
.el-tree-select-popper .el-button--small {
|
||||
width: 25px !important;
|
||||
min-width: 25px !important;
|
||||
}
|
||||
|
||||
.el-tree-select-popper[x-placement^='bottom'] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.mb10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
color: #cccccc;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
78
frontend/src/components/ElTreeSelect/utils.js
Normal file
78
frontend/src/components/ElTreeSelect/utils.js
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @moduleName:通用工具类
|
||||
* @Author: dawdler
|
||||
* @Date: 2019-01-09 15:30:18
|
||||
* @LastModifiedBy: dawdler
|
||||
* @LastEditTime: 2020-12-26 14:06:09
|
||||
*/
|
||||
export default {
|
||||
getTreeData,
|
||||
each
|
||||
}
|
||||
/*
|
||||
each(arr, (item, children) => {
|
||||
item.value = xx;
|
||||
// 该item 包含children,因此直接赋值,不需要单独处理children里面的值
|
||||
});
|
||||
* [_each description] 倒查、展平、数据回调返回回当前一条数据和子集
|
||||
* @param {[Array]} data [description]
|
||||
* @param {Function} callback [description]
|
||||
* @param {String} childName[description]
|
||||
* @return {[Array]} [description]
|
||||
* 默认使用副本,在callback处理数据,如果不使用副本,那么需要重新对treeData赋值
|
||||
treeData = each(treeData, (item, children) => {
|
||||
item.value = xx;
|
||||
});
|
||||
*/
|
||||
export function each(data, callback, childName = 'children') {
|
||||
let current
|
||||
let children
|
||||
for (let i = 0, len = data.length; i < len; i++) {
|
||||
current = data[i]
|
||||
children = []
|
||||
if (current[childName] && current[childName].length > 0) {
|
||||
children = current[childName]
|
||||
}
|
||||
callback && callback(current, children)
|
||||
if (children.length > 0) {
|
||||
each(children, callback, childName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author yihuang",
|
||||
* @param data 数据
|
||||
* @param id 要比对的名称
|
||||
* @param val 要比对的值
|
||||
* @param name 要返回的名称
|
||||
* @param children 子集名称
|
||||
* @param isRow 是否返回这一行的数据
|
||||
* @注 迭代判断多层
|
||||
* //=======================
|
||||
* 返回这一条数据的中文名
|
||||
* let name=utils.getTreeData(arr, 'flowId', item.decategoryId, 'name');
|
||||
* //=======================
|
||||
* 返回所有匹配的数据
|
||||
* let arr=utils.getTreeData(arr, 'flowId', item.decategoryId, 'name','children',true);
|
||||
*/
|
||||
export function getTreeData(data, id = 'id', val = '', name = 'name', children = 'children', isRow = false) {
|
||||
const arr = []
|
||||
each(
|
||||
data,
|
||||
item => {
|
||||
if (item[id] === val) {
|
||||
arr.push(item)
|
||||
}
|
||||
},
|
||||
children
|
||||
)
|
||||
return arr.length > 0 ? (isRow ? arr : arr[0][name]) : null
|
||||
}
|
||||
export function guid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
331
frontend/src/components/widget/DeWidget/DeSelectTree.vue
Normal file
331
frontend/src/components/widget/DeWidget/DeSelectTree.vue
Normal file
@ -0,0 +1,331 @@
|
||||
<template>
|
||||
|
||||
<el-tree-select
|
||||
v-if="element.options!== null && element.options.attrs!==null && show"
|
||||
ref="deSelectTree"
|
||||
v-model="value"
|
||||
popover-class="test-class-wrap"
|
||||
:is-single="isSingle"
|
||||
:data="datas"
|
||||
:select-params="selectParams"
|
||||
:tree-params="treeParams"
|
||||
:filter-node-method="_filterFun"
|
||||
:tree-render-fun="_renderFun"
|
||||
@searchFun="_searchFun"
|
||||
@node-click="changeNode"
|
||||
@removeTag="changeNodeIds"
|
||||
@check="changeCheckNode"
|
||||
@select-clear="selectClear"
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mappingFieldValues, linkMappingFieldValues } from '@/api/dataset/dataset'
|
||||
import bus from '@/utils/bus'
|
||||
import { getLinkToken, getToken } from '@/utils/auth'
|
||||
import ElTreeSelect from '@/components/ElTreeSelect'
|
||||
export default {
|
||||
components: { ElTreeSelect },
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
inDraw: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
inScreen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
size: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showNumber: false,
|
||||
selectOptionWidth: 0,
|
||||
show: true,
|
||||
datas: [],
|
||||
value: this.isSingle ? '' : [],
|
||||
selectParams: {
|
||||
clearable: true,
|
||||
placeholder: this.$t(this.element.options.attrs.placeholder)
|
||||
},
|
||||
treeParams: {
|
||||
showParent: true,
|
||||
clickParent: true,
|
||||
filterable: true,
|
||||
// 只想要子节点,不需要父节点
|
||||
leafOnly: false,
|
||||
includeHalfChecked: false,
|
||||
'check-strictly': false,
|
||||
'default-expand-all': false,
|
||||
'expand-on-click-node': false,
|
||||
'render-content': this._renderFun,
|
||||
data: [],
|
||||
props: {
|
||||
children: 'children',
|
||||
label: 'text',
|
||||
rootId: 'root',
|
||||
disabled: 'disabled',
|
||||
parentId: 'pid',
|
||||
value: 'id'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
operator() {
|
||||
return this.element.options.attrs.multiple ? 'in' : 'eq'
|
||||
},
|
||||
defaultValueStr() {
|
||||
if (!this.element || !this.element.options || !this.element.options.value) return ''
|
||||
return this.element.options.value.toString()
|
||||
},
|
||||
viewIds() {
|
||||
if (!this.element || !this.element.options || !this.element.options.attrs.viewIds) return ''
|
||||
return this.element.options.attrs.viewIds.toString()
|
||||
},
|
||||
manualModify() {
|
||||
return !!this.element.options.manualModify
|
||||
},
|
||||
panelInfo() {
|
||||
return this.$store.state.panel.panelInfo
|
||||
},
|
||||
isSingle() {
|
||||
return this.element.options.attrs.multiple
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'viewIds': function(value, old) {
|
||||
if (typeof value === 'undefined' || value === old) return
|
||||
this.setCondition()
|
||||
},
|
||||
'defaultValueStr': function(value, old) {
|
||||
if (value === old) return
|
||||
this.value = this.fillValueDerfault()
|
||||
this.changeValue(value)
|
||||
},
|
||||
'element.options.attrs.fieldId': function(value, old) {
|
||||
if (value === null || typeof value === 'undefined' || value === old) return
|
||||
this.datas = []
|
||||
|
||||
let method = mappingFieldValues
|
||||
const token = this.$store.getters.token || getToken()
|
||||
const linkToken = this.$store.getters.linkToken || getLinkToken()
|
||||
if (!token && linkToken) {
|
||||
method = linkMappingFieldValues
|
||||
}
|
||||
const param = { fieldIds: this.element.options.attrs.fieldId.split(',') }
|
||||
if (this.panelInfo.proxy) {
|
||||
param.userId = this.panelInfo.proxy
|
||||
}
|
||||
this.element.options.attrs.fieldId &&
|
||||
this.element.options.attrs.fieldId.length > 0 &&
|
||||
method(param).then(res => {
|
||||
this.datas = this.optionDatas(res.data)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.deSelectTree.treeDataUpdateFun(this.datas)
|
||||
})
|
||||
}) || (this.element.options.value = '')
|
||||
},
|
||||
'element.options.attrs.multiple': function(value, old) {
|
||||
if (typeof old === 'undefined' || value === old) return
|
||||
if (!this.inDraw) {
|
||||
this.value = value ? [] : null
|
||||
this.element.options.value = ''
|
||||
}
|
||||
|
||||
this.show = false
|
||||
this.$nextTick(() => {
|
||||
this.show = true
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.initLoad()
|
||||
},
|
||||
mounted() {
|
||||
bus.$on('onScroll', () => {
|
||||
|
||||
})
|
||||
bus.$on('reset-default-value', id => {
|
||||
if (this.inDraw && this.manualModify && this.element.id === id) {
|
||||
this.value = this.fillValueDerfault()
|
||||
this.changeValue(this.value)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectClear() {
|
||||
this.changeValue(this.value)
|
||||
},
|
||||
changeNode(data, node) {
|
||||
this.changeValue(this.value)
|
||||
},
|
||||
changeCheckNode(data, obj) {
|
||||
const { checkedKeys } = obj
|
||||
if (checkedKeys) this.value = checkedKeys
|
||||
this.changeValue(this.value)
|
||||
},
|
||||
changeNodeIds(ids) {
|
||||
this.value = ids
|
||||
this.changeValue(this.value)
|
||||
},
|
||||
initLoad() {
|
||||
this.value = this.fillValueDerfault()
|
||||
this.datas = []
|
||||
if (this.element.options.attrs.fieldId) {
|
||||
let method = mappingFieldValues
|
||||
const token = this.$store.getters.token || getToken()
|
||||
const linkToken = this.$store.getters.linkToken || getLinkToken()
|
||||
if (!token && linkToken) {
|
||||
method = linkMappingFieldValues
|
||||
}
|
||||
method({ fieldIds: this.element.options.attrs.fieldId.split(',') }).then(res => {
|
||||
this.datas = this.optionDatas(res.data)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.deSelectTree.treeDataUpdateFun(this.datas)
|
||||
})
|
||||
})
|
||||
}
|
||||
if (this.element.options.value) {
|
||||
this.value = this.fillValueDerfault()
|
||||
this.changeValue(this.value)
|
||||
}
|
||||
},
|
||||
changeValue(value) {
|
||||
if (!this.inDraw) {
|
||||
if (value === null) {
|
||||
this.element.options.value = ''
|
||||
} else {
|
||||
this.element.options.value = Array.isArray(value) ? value.join() : value
|
||||
}
|
||||
this.element.options.manualModify = false
|
||||
} else {
|
||||
this.element.options.manualModify = true
|
||||
}
|
||||
this.setCondition()
|
||||
this.showNumber = false
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (!this.element.options.attrs.multiple || !this.$refs.deSelect || !this.$refs.deSelect.$refs.tags) {
|
||||
return
|
||||
}
|
||||
const kids = this.$refs.deSelect.$refs.tags.children[0].children
|
||||
let contentWidth = 0
|
||||
kids.forEach(kid => {
|
||||
contentWidth += kid.offsetWidth
|
||||
})
|
||||
this.showNumber = contentWidth > ((this.$refs.deSelectTree.$refs.tags.clientWidth - 30) * 0.9)
|
||||
})
|
||||
},
|
||||
|
||||
setCondition() {
|
||||
const param = {
|
||||
component: this.element,
|
||||
value: this.formatFilterValue(),
|
||||
operator: this.operator
|
||||
}
|
||||
this.inDraw && this.$store.commit('addViewFilter', param)
|
||||
},
|
||||
formatFilterValue() {
|
||||
const SEPARATOR = '-de-'
|
||||
if (this.value === null) return []
|
||||
if (Array.isArray(this.value)) {
|
||||
const results = []
|
||||
const duplicateMap = {}
|
||||
this.value.forEach(item => {
|
||||
const links = item.split(SEPARATOR)
|
||||
let temp = ''
|
||||
for (let index = 0; index < links.length; index++) {
|
||||
const isLast = index === (links.length - 1)
|
||||
const isFirst = index === 0
|
||||
const node = links[index]
|
||||
|
||||
temp += ((isFirst ? '' : SEPARATOR) + node)
|
||||
if (duplicateMap[temp] && !isLast) {
|
||||
delete duplicateMap[temp]
|
||||
}
|
||||
}
|
||||
|
||||
duplicateMap[item] = true
|
||||
})
|
||||
for (const key in duplicateMap) {
|
||||
if (Object.hasOwnProperty.call(duplicateMap, key) && duplicateMap[key]) {
|
||||
const node = key.replaceAll(SEPARATOR, ',')
|
||||
results.push(node)
|
||||
}
|
||||
}
|
||||
return results
|
||||
// return this.value
|
||||
}
|
||||
return this.value.split(',')
|
||||
},
|
||||
|
||||
fillValueDerfault() {
|
||||
const defaultV = this.element.options.value === null ? '' : this.element.options.value.toString()
|
||||
if (this.element.options.attrs.multiple) {
|
||||
if (defaultV === null || typeof defaultV === 'undefined' || defaultV === '' || defaultV === '[object Object]') return []
|
||||
return defaultV.split(',')
|
||||
} else {
|
||||
if (defaultV === null || typeof defaultV === 'undefined' || defaultV === '' || defaultV === '[object Object]') return null
|
||||
return defaultV.split(',')[0]
|
||||
}
|
||||
},
|
||||
optionDatas(datas) {
|
||||
if (!datas) return null
|
||||
|
||||
return datas.filter(item => !!item)
|
||||
},
|
||||
|
||||
setOptionWidth(event) {
|
||||
// 下拉框弹出时,设置弹框的宽度
|
||||
this.$nextTick(() => {
|
||||
// this.selectOptionWidth = event.srcElement.offsetWidth + 'px'
|
||||
this.selectOptionWidth = event.srcElement.parentElement.parentElement.offsetWidth + 'px'
|
||||
})
|
||||
},
|
||||
|
||||
/* 下面是树的渲染方法 */
|
||||
|
||||
_filterFun(value, data, node) {
|
||||
if (!value) return true
|
||||
return data.id.toString().indexOf(value.toString()) !== -1
|
||||
},
|
||||
// 树点击
|
||||
_nodeClickFun(data, node, vm) {
|
||||
console.log('this _nodeClickFun', this.value, data, node)
|
||||
},
|
||||
// 树过滤
|
||||
_searchFun(value) {
|
||||
console.log(value, '<--_searchFun')
|
||||
// 自行判断 是走后台查询,还是前端过滤
|
||||
this.$refs.treeSelect.filterFun(value)
|
||||
// 后台查询
|
||||
// this.$refs.treeSelect.treeDataUpdateFun(treeData);
|
||||
},
|
||||
// 自定义render
|
||||
_renderFun(h, { node, data, store }) {
|
||||
const { props, clickParent } = this.treeParams
|
||||
return (
|
||||
<span class={['custom-tree-node', !clickParent && data[props.children] && data[props.children].length ? 'disabled' : null]}>
|
||||
<span>{node.label}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,108 @@
|
||||
|
||||
import { WidgetService } from '../service/WidgetService'
|
||||
const leftPanel = {
|
||||
icon: 'iconfont icon-xialashu',
|
||||
label: 'detextselectTree.label',
|
||||
defaultClass: 'text-filter'
|
||||
}
|
||||
|
||||
const dialogPanel = {
|
||||
options: {
|
||||
attrs: {
|
||||
multiple: false,
|
||||
placeholder: 'detextselectTree.placeholder',
|
||||
viewIds: [],
|
||||
datas: [],
|
||||
key: 'id',
|
||||
label: 'text',
|
||||
value: 'id',
|
||||
fieldId: '',
|
||||
dragItems: []
|
||||
},
|
||||
value: '',
|
||||
manualModify: false
|
||||
},
|
||||
defaultClass: 'text-filter',
|
||||
component: 'de-select-tree',
|
||||
miniSizex: 1,
|
||||
miniSizey: 1
|
||||
}
|
||||
const drawPanel = {
|
||||
type: 'custom',
|
||||
style: {
|
||||
width: 300,
|
||||
height: 90,
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
lineHeight: '',
|
||||
letterSpacing: 0,
|
||||
textAlign: '',
|
||||
color: '',
|
||||
hPosition: 'left',
|
||||
vPosition: 'center'
|
||||
},
|
||||
component: 'de-select-tree'
|
||||
}
|
||||
|
||||
class TextSelectTreeServiceImpl extends WidgetService {
|
||||
constructor(options = {}) {
|
||||
Object.assign(options, { name: 'textSelectTreeWidget' })
|
||||
super(options)
|
||||
this.filterDialog = true
|
||||
this.showSwitch = true
|
||||
}
|
||||
|
||||
initLeftPanel() {
|
||||
const value = JSON.parse(JSON.stringify(leftPanel))
|
||||
return value
|
||||
}
|
||||
|
||||
initFilterDialog() {
|
||||
const value = JSON.parse(JSON.stringify(dialogPanel))
|
||||
return value
|
||||
}
|
||||
|
||||
initDrawPanel() {
|
||||
const value = JSON.parse(JSON.stringify(drawPanel))
|
||||
return value
|
||||
}
|
||||
|
||||
filterFieldMethod(fields) {
|
||||
return fields.filter(field => {
|
||||
return field['deType'] === 0
|
||||
})
|
||||
}
|
||||
|
||||
optionDatas(datas) {
|
||||
if (!datas) return null
|
||||
return datas.filter(item => !!item).map(item => {
|
||||
return {
|
||||
id: item,
|
||||
text: item
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getParam(element) {
|
||||
const value = this.fillValueDerfault(element)
|
||||
const param = {
|
||||
component: element,
|
||||
value: !value ? [] : Array.isArray(value) ? value : value.toString().split(','),
|
||||
operator: element.options.attrs.multiple ? 'in' : 'eq'
|
||||
}
|
||||
return param
|
||||
}
|
||||
|
||||
fillValueDerfault(element) {
|
||||
const defaultV = element.options.value === null ? '' : element.options.value.toString()
|
||||
if (element.options.attrs.multiple) {
|
||||
if (defaultV === null || typeof defaultV === 'undefined' || defaultV === '' || defaultV === '[object Object]') return []
|
||||
return defaultV.split(',')
|
||||
} else {
|
||||
if (defaultV === null || typeof defaultV === 'undefined' || defaultV === '' || defaultV === '[object Object]') return null
|
||||
return defaultV.split(',')[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
const textSelectTreeServiceImpl = new TextSelectTreeServiceImpl()
|
||||
export default textSelectTreeServiceImpl
|
@ -1886,6 +1886,10 @@ export default {
|
||||
label: 'Text selector',
|
||||
placeholder: 'Please select'
|
||||
},
|
||||
detextselectTree: {
|
||||
label: 'Tree selector',
|
||||
placeholder: 'Please select'
|
||||
},
|
||||
detextgridselect: {
|
||||
label: 'Text list',
|
||||
placeholder: 'Please select'
|
||||
|
@ -1898,6 +1898,10 @@ export default {
|
||||
label: '文本下拉',
|
||||
placeholder: '請選擇'
|
||||
},
|
||||
detextselectTree: {
|
||||
label: '下拉树',
|
||||
placeholder: '請選擇'
|
||||
},
|
||||
detextgridselect: {
|
||||
label: '文本列錶',
|
||||
placeholder: '請選擇'
|
||||
|
@ -1331,7 +1331,7 @@ export default {
|
||||
sql_ds_union_error: '直连模式下SQL数据集,不支持关联',
|
||||
api_data: 'API 数据集'
|
||||
},
|
||||
driver:{
|
||||
driver: {
|
||||
driver: '驱动',
|
||||
please_choose_driver: '请选择驱动',
|
||||
mgm: '驱动管理',
|
||||
@ -1906,6 +1906,10 @@ export default {
|
||||
label: '文本下拉',
|
||||
placeholder: '请选择'
|
||||
},
|
||||
detextselectTree: {
|
||||
label: '下拉树',
|
||||
placeholder: '请选择'
|
||||
},
|
||||
detextgridselect: {
|
||||
label: '文本列表',
|
||||
placeholder: '请选择'
|
||||
|
@ -55,7 +55,8 @@ export default {
|
||||
'文本过滤组件': [
|
||||
'textSelectWidget',
|
||||
'textSelectGridWidget',
|
||||
'textInputWidget'
|
||||
'textInputWidget',
|
||||
'textSelectTreeWidget'
|
||||
],
|
||||
'数字过滤组件': [
|
||||
'numberSelectWidget',
|
||||
|
150
frontend/src/views/system/test/MyTree.vue
Normal file
150
frontend/src/views/system/test/MyTree.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<!--
|
||||
* @Author: dawdler
|
||||
* @Date: 2020-12-26 11:52:05
|
||||
* @Description: demo
|
||||
* @LastModifiedBy: dawdler
|
||||
-->
|
||||
<template>
|
||||
<el-tree-select
|
||||
ref="treeSelect"
|
||||
v-model="values"
|
||||
popover-class="test-class-wrap"
|
||||
:styles="styles"
|
||||
:select-params="selectParams"
|
||||
:tree-params="treeParams"
|
||||
:filter-node-method="_filterFun"
|
||||
:tree-render-fun="_renderFun"
|
||||
@searchFun="_searchFun"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import ElTreeSelect from '@/components/ElTreeSelect'
|
||||
export default {
|
||||
name: 'MyTreeSelect',
|
||||
components: { ElTreeSelect },
|
||||
props: {
|
||||
params: Object,
|
||||
isSingle: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
styles: {
|
||||
width: '300px'
|
||||
},
|
||||
// 单选value为字符串,多选为数组
|
||||
values: this.isSingle ? '' : [],
|
||||
selectParams: {
|
||||
clearable: true,
|
||||
placeholder: '请输入内容'
|
||||
},
|
||||
treeParams: {
|
||||
clickParent: false,
|
||||
filterable: true,
|
||||
// 只想要子节点,不需要父节点
|
||||
leafOnly: false,
|
||||
includeHalfChecked: false,
|
||||
'check-strictly': false,
|
||||
'default-expand-all': true,
|
||||
'expand-on-click-node': false,
|
||||
'render-content': this._renderFun,
|
||||
data: [],
|
||||
props: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
rootId: '0',
|
||||
disabled: 'disabled',
|
||||
parentId: 'parentId',
|
||||
value: 'id'
|
||||
},
|
||||
...this.params
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
created() {},
|
||||
mounted() {
|
||||
// 手动更新树数据
|
||||
const data = []
|
||||
const { label, children, parentId, value, rootId } = this.treeParams.props
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const rootNode = {
|
||||
[label]: `节点${i}`,
|
||||
[parentId]: rootId,
|
||||
[value]: i,
|
||||
[children]: []
|
||||
}
|
||||
for (let a = 0; a < 5; a++) {
|
||||
const subId = `${rootNode[value]}_${a}`
|
||||
const subNode = {
|
||||
[label]: `子节点${subId}`,
|
||||
[parentId]: rootNode[value],
|
||||
[value]: subId,
|
||||
[children]: []
|
||||
}
|
||||
for (let b = 0; b < 5; b++) {
|
||||
const endId = `${subId}_${b}`
|
||||
const endNode = {
|
||||
[label]: `末级节点${endId}`,
|
||||
[parentId]: subNode[value],
|
||||
[value]: endId,
|
||||
[children]: []
|
||||
}
|
||||
subNode[children].push(endNode)
|
||||
}
|
||||
rootNode[children].push(subNode)
|
||||
}
|
||||
data.push(rootNode)
|
||||
}
|
||||
const myNode = {
|
||||
[label]: '测试超长节点啊啊啊测试超长节点啊啊啊测试超长节点啊啊啊测试超长节点啊啊啊测试超长节点啊啊啊测试超长节点啊啊啊',
|
||||
[parentId]: rootId,
|
||||
[value]: 1000,
|
||||
[children]: []
|
||||
}
|
||||
data.push(myNode)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.treeSelect.treeDataUpdateFun(data)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
_filterFun(value, data, node) {
|
||||
if (!value) return true
|
||||
return data.id.toString().indexOf(value.toString()) !== -1
|
||||
},
|
||||
// 树点击
|
||||
_nodeClickFun(data, node, vm) {
|
||||
console.log('this _nodeClickFun', this.values, data, node)
|
||||
},
|
||||
// 树过滤
|
||||
_searchFun(value) {
|
||||
console.log(value, '<--_searchFun')
|
||||
// 自行判断 是走后台查询,还是前端过滤
|
||||
this.$refs.treeSelect.filterFun(value)
|
||||
// 后台查询
|
||||
// this.$refs.treeSelect.treeDataUpdateFun(treeData);
|
||||
},
|
||||
// 自定义render
|
||||
_renderFun(h, { node, data, store }) {
|
||||
const { props, clickParent } = this.treeParams
|
||||
return (
|
||||
<span class={['custom-tree-node', !clickParent && data[props.children] && data[props.children].length ? 'disabled' : null]}>
|
||||
<span>{node.label}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.disabled {
|
||||
cursor: no-drop;
|
||||
}
|
||||
.custom-tree-node {
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
</style>
|
69
frontend/src/views/system/test/index.vue
Normal file
69
frontend/src/views/system/test/index.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<!--
|
||||
* @moduleName: 测试el-tree-select
|
||||
* @Author: dawdler
|
||||
* @Date: 2018-12-19 14:03:03
|
||||
* @LastModifiedBy: dawdler
|
||||
-->
|
||||
<template>
|
||||
<div id="app111">
|
||||
<el-form label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单选">
|
||||
<MyTree :is-single="true" :params="{ clickParent: true, showParent: true }" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="多选">
|
||||
<MyTree :params="{ clickParent: true, showParent: true }" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="弹框关闭调试">
|
||||
<el-select v-model="test" multiple placeholder="请选择" @change="_selectChange">
|
||||
<el-option v-for="item in testData" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<div>
|
||||
测试焦点触发
|
||||
<svg>
|
||||
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
|
||||
</svg>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import MyTree from './MyTree'
|
||||
export default {
|
||||
name: 'App111',
|
||||
|
||||
components: { MyTree },
|
||||
data() {
|
||||
return {
|
||||
test: '',
|
||||
testData: ['test1', 'test2']
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
// 下拉框修改
|
||||
_selectChange(val) {
|
||||
console.log(val, '<-select change')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
#app111 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.el-select {
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user