Commit 9c771c27 authored by 李纪文's avatar 李纪文

fix: 设备树修改

parent c7de274b
...@@ -124,6 +124,7 @@ export default { ...@@ -124,6 +124,7 @@ export default {
title: '业务数据展示', title: '业务数据展示',
children: [ children: [
'EC_DeviceTree', 'EC_DeviceTree',
'EC_DeviceTreeGroup',
'EC_QuotaSelect', 'EC_QuotaSelect',
'EC_HistoryInfo', 'EC_HistoryInfo',
'EC_RealTimeInfo', 'EC_RealTimeInfo',
......
...@@ -27,34 +27,13 @@ group: ...@@ -27,34 +27,13 @@ group:
api 参考 Antd Tree 组件 https://ant.design/components/tree-cn/ api 参考 Antd Tree 组件 https://ant.design/components/tree-cn/
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| ------------- | -------------------------------- | --------------------- | -------- | | --- | --- | --- | --- |
| deviceTypes | 设备类型, 多种设备逗号隔开 | string | 二供泵房 | | prefix | 搜索框的前置图标 | ReactNode | 搜索 icon |
| userAccessor | 是否启用设备权限过滤 | boolean | false | | placeholder | 搜索框占位符 | string | 搜索设备名称 |
| getChild | 是否查询子设备 | boolean | false | | checkable | 节点前添加 Checkbox 复选框 | boolean | false |
| sortFields | 设备排序字段, 设备台账表中的字段 | string | - | | pagination | 默认分页,false 取消分页 | boolean | true |
| direction | 设备排序方向, asc/desc | string | - | | serviceParams | 服务参数 | object | { pageIndex: 1, pageSize: 500, deviceTypes: '二供泵房,二供机组', getChild: true, userID: 1, queryInfo: '', sortFields: '', direction: '', isTop: true } |
| classField | 分组字段,设备台账表中的字段 | string | - | | deviceTreeService `必需` | 设备树服务 | promise | - |
| customerName | 服务参数 | string | - | | onTreeCheck | 点击复选框触发 | function(checkedNodes){ } | - |
| onSelect | 选择回调 | function(data: []){ } | - | | onTreeSelect | 点击树节点触发 | function(selectedNodes){ } | - |
| onCheck | 多选回调 | function(data: []){ } | - |
| setDeviceList | 返回设备回调 | function(data: []){ } | - |
| setSearchStr | 返回搜索字符串 | function(val: ''){ } | - |
| selectable | 'Antd-Tree 属性' | function(data: []){ } | - |
| checkable | 'Antd-Tree 属性' | function(data: []){ } | - |
| keepChecked | 是否选择第一个 | boolean | false |
## 补充说明
- keepChecked: boolean, 搜索之后保持之前的选中数据
- 第一次加载数据后默认勾选第一条
- 再次加载数据后保持之前的选中数据,不默认勾选第一条
- 选中数据之后和之前的做合并处理,key 也做合并处理做保留
- 潜在问题 1: 取消选中后,区分取消选中的数据 和 之前保留但不再树中的数据(利用 onCheck 会保留多余的 key)
- singleType: boolean, 是否只能选单一类型设备, 其他类型设备会被禁用
- 单选及多选说明:
- 仅多选: 传递 checkable=true, selectable=false, 触发 onCheck
- 仅单选: 传递 checkable=false, selectable=true, 触发 onSelect
- 混合: 传递 checkable=true, selectable=true, 触发 onCheck(混合模式表现:点击前面多选框可多选设备, 点击设备名称可直接切换单选到该设备)
import React from 'react'; import React from 'react';
import DeviceTree from '../index'; import PandaDeviceTree from '../index';
import { service } from '@wisdom-utils/utils';
const REQUEST_HTTP = 'http';
const REQUEST_METHOD_POST = 'post';
const GET_DEVICE_LIST =
'https://www.fastmock.site/mock/162c15dca15c4dba9ba51e0a0b76929b/api/Publish/GCK/Device/DeviceTree'; //获取设备树列表
// const GET_DEVICE_LIST = '/api/Publish/GCK/Device/DeviceTree'; //获取设备树列表
const deviceTreeService = {
getDeviceList: {
url: GET_DEVICE_LIST,
method: REQUEST_METHOD_POST,
type: REQUEST_HTTP,
},
};
const dtService = service(deviceTreeService);
const getDeviceList = dtService.getDeviceList;
const Demo = () => { const Demo = () => {
const onTreeCheck = (checkedKeysValue) => { const onTreeCheck = (checkedKeysValue) => {
...@@ -12,20 +33,23 @@ const Demo = () => { ...@@ -12,20 +33,23 @@ const Demo = () => {
return ( return (
<div style={{ width: '200px', height: '400px', border: '1px solid #eee' }}> <div style={{ width: '200px', height: '400px', border: '1px solid #eee' }}>
<DeviceTree <PandaDeviceTree
onCheck={(data) => {console.log(data)}} checkable
onSelect={(data) => {console.log(data)}} onTreeCheck={onTreeCheck}
checkable={true} onTreeSelect={onTreeSelect}
selectable={true} deviceTreeService={getDeviceList}
deviceTypes={'压力表,熊猫水表,二供泵房'} serviceParams={{
sortFields={''} pageIndex: 1,
direction={''} pageSize: 20,
classField={'所属分区'} // deviceTypes: '二供泵房,二供机组',
getChild={true} getChild: true,
userAccessor={false} userID: 1,
keepChecked // queryInfo: '',
setDeviceList={() => {}} // sortFields: '',
setSearchStr={() => {}} // direction: '',
// isTop: true,
}}
// pagination={false}
/> />
</div> </div>
); );
......
...@@ -3,18 +3,70 @@ ...@@ -3,18 +3,70 @@
@ec-device-tree-prefix-cls: ~'@{ant-prefix}-ec-device-tree'; @ec-device-tree-prefix-cls: ~'@{ant-prefix}-ec-device-tree';
.@{ec-device-tree-prefix-cls} { .@{ec-device-tree-prefix-cls} {
display: flex;
flex-direction: column;
width: 100%;
height: 100%; height: 100%;
overflow: hidden; padding: 5px;
padding: 10px;
&-tree-wrap { &-divider {
height: calc(100% - 48px); margin: 6px 0 12px 0;
overflow: auto;
} }
:global { .@{ant-prefix}-tree-checkbox {
.@{ant-prefix}-input-search { margin: 7px 2px 0 0;
margin-bottom: 16px;
}
} }
}
\ No newline at end of file .@{ant-prefix}-tree-title {
white-space: nowrap;
}
.@{ant-prefix}-tree-treenode {
width: 100%;
}
.@{ant-prefix}-tree-switcher {
line-height: 30px;
}
.@{ant-prefix}-tree-switcher-noop {
width: 14px;
}
.@{ant-prefix}-tree-node-content-wrapper {
flex: 1;
min-height: 30px;
overflow: hidden;
line-height: 30px;
text-overflow: ellipsis;
border-radius: 3px;
}
.@{ant-prefix}-tree .@{ant-prefix}-tree-node-content-wrapper.@{ant-prefix}-tree-node-selected {
color: #1890ff;
background-color: #eaf1fe;
}
&-content {
flex: 1;
overflow-y: scroll;
}
.@{ant-prefix}-spin-nested-loading {
height: 100%;
}
&-pagination {
margin: 10px auto;
}
&-total.@{ant-prefix}-divider-horizontal.@{ant-prefix}-divider-with-text {
margin: 10px auto;
color: #a4b1c7;
}
.@{ant-prefix}-divider-horizontal.@{ant-prefix}-divider-with-text::before,
.@{ant-prefix}-divider-horizontal.@{ant-prefix}-divider-with-text::after {
top: 0;
}
}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc
# production
**/dist
/.vscode
**/**/lib/**
**/**/es/**
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/coverage
.idea
package-lock.json
*bak
.vscode
# visual studio code
.history
*.log
functions/mock
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase
.eslintcache
.changelog
# devServer-host
host/
webpack.host.js
\ No newline at end of file
# `@wisdom-components/ec_devicetreegroup`
> TODO: description
## Usage
```
import ECDeviceTree from '@wisdom-components/ec_devicetreegroup';
// TODO: DEMONSTRATE API
```
{
"name": "@wisdom-components/ec_devicetreegroup",
"version": "1.0.0",
"description": "> TODO: description",
"author": "lijiwen <961370825@qq.com>",
"homepage": "",
"license": "ISC",
"sideEffects": [
"*.less"
],
"module": "es/index.js",
"main": "lib/index.js",
"files": [
"lib",
"es",
"dist"
],
"directories": {
"lib": "lib",
"es": "es",
"dist": "dist",
"test": "__tests__"
},
"publishConfig": {
"registry": "https://g.civnet.cn:4873/"
},
"repository": {
"type": "git",
"url": "https://g.civnet.cn:8443/ReactWeb5/wisdom-components.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@babel/runtime": "^7.17.9"
}
}
---
title: EC_DeviceTreeGroup - 设备树分组
nav:
title: 业务组件
path: /extend-components
group:
path: /
---
# EC_DeviceTreeGroup 设备树
基础业务组件
- 允许单设备选择
- 允许多设备选择
- 允许搜索设备树
## 何时使用
- 在设备树选择时。
## 代码演示
<code src="./demos/Basic.tsx">
## API
api 参考 Antd Tree 组件 https://ant.design/components/tree-cn/
| 参数 | 说明 | 类型 | 默认值 |
| ------------- | -------------------------------- | --------------------- | -------- |
| deviceTypes | 设备类型, 多种设备逗号隔开 | string | 二供泵房 |
| userAccessor | 是否启用设备权限过滤 | boolean | false |
| getChild | 是否查询子设备 | boolean | false |
| sortFields | 设备排序字段, 设备台账表中的字段 | string | - |
| direction | 设备排序方向, asc/desc | string | - |
| classField | 分组字段,设备台账表中的字段 | string | - |
| customerName | 服务参数 | string | - |
| onSelect | 选择回调 | function(data: []){ } | - |
| onCheck | 多选回调 | function(data: []){ } | - |
| setDeviceList | 返回设备回调 | function(data: []){ } | - |
| setSearchStr | 返回搜索字符串 | function(val: ''){ } | - |
| selectable | 'Antd-Tree 属性' | function(data: []){ } | - |
| checkable | 'Antd-Tree 属性' | function(data: []){ } | - |
| keepChecked | 是否选择第一个 | boolean | false |
## 补充说明
- keepChecked: boolean, 搜索之后保持之前的选中数据
- 第一次加载数据后默认勾选第一条
- 再次加载数据后保持之前的选中数据,不默认勾选第一条
- 选中数据之后和之前的做合并处理,key 也做合并处理做保留
- 潜在问题 1: 取消选中后,区分取消选中的数据 和 之前保留但不再树中的数据(利用 onCheck 会保留多余的 key)
- singleType: boolean, 是否只能选单一类型设备, 其他类型设备会被禁用
- 单选及多选说明:
- 仅多选: 传递 checkable=true, selectable=false, 触发 onCheck
- 仅单选: 传递 checkable=false, selectable=true, 触发 onSelect
- 混合: 传递 checkable=true, selectable=true, 触发 onCheck(混合模式表现:点击前面多选框可多选设备, 点击设备名称可直接切换单选到该设备)
import React from 'react';
import DeviceTree from '../index';
const Demo = () => {
const onTreeCheck = (checkedKeysValue) => {
console.log('onTreeCheck', checkedKeysValue);
};
const onTreeSelect = (selectedKeysValue) => {
console.log('onTreeSelect', selectedKeysValue);
};
return (
<div style={{ width: '200px', height: '400px', border: '1px solid #eee' }}>
<DeviceTree
onCheck={(data) => {
console.log(data);
}}
onSelect={(data) => {
console.log(data);
}}
checkable={true}
selectable={true}
deviceTypes={'压力表,熊猫水表,二供泵房'}
sortFields={''}
direction={''}
classField={'所属分区'}
getChild={true}
userAccessor={false}
keepChecked
setDeviceList={() => {}}
setSearchStr={() => {}}
/>
</div>
);
};
export default Demo;
import PropTypes from 'prop-types';
import { Input, Layout, Spin, Tree, ConfigProvider } from 'antd';
// import { SearchOutlined } from '@ant-design/icons';
import React, { useCallback, useEffect, useMemo, useRef, useState, useContext } from 'react';
import PandaEmpty from '@wisdom-components/empty';
import classNames from 'classnames';
import _ from 'lodash';
import { getEquipmentInfo } from './apis';
import './index.less';
import classnames from 'classnames';
// 生成树的数据格式
const treeDataGenerator = (origin, currentType, ref) => {
if (!origin || !Array.isArray(origin)) return [];
return origin.map((item) => {
const { shortName, deviceName, code, deviceType, children: originChildren } = item;
const title = shortName || deviceName;
const children = treeDataGenerator(originChildren, currentType);
const _width = (ref?.current?.getBoundingClientRect().width || 180) - 24 - 8 - 28; // 24为tree的左侧空白,8为右侧滚动条位置,28是左侧复选框和边距等
return {
...item,
title: (
<div
title={title}
style={{
width: _width,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{title}
</div>
),
key: code,
children,
disabled: currentType ? !(currentType === deviceType) : false,
};
});
};
// 树结构展开平铺
const flattenTreeData = (treeData) => {
if (!treeData || !Array.isArray(treeData)) return {};
const result = {};
const deep = (treeData, target) => {
treeData &&
treeData.forEach((item) => {
target[item.key] = item;
if (item.children && item.children.length > 0) {
deep(item.children, target);
}
});
};
deep(treeData, result);
return result;
};
// 图层树处理
const convertToTree = (data, name) => {
let obj = {};
let newData = [];
data.forEach((item, index) => {
const state = item[name] || '未知';
if (obj[state]) {
obj[state] = [...obj[state], item];
} else {
obj[state] = [item];
}
});
for (let k in obj) {
newData.push({
title: k,
children: [...obj[k]],
key: k,
selectable: false,
disableCheckbox: true,
checkable: false,
});
expandedKeys.push(k);
}
return newData;
};
let dataList = [];
let expandedKeys = [];
const DeviceTree = (props) => {
const {
deviceTypes = '二供泵房,二供机组',
userAccessor = false,
getChild = true,
sortFields = '',
direction = '',
classField = '',
customerName = '',
} = props;
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('ec-device-tree-group');
const cusProps = _.pick(props, ['onSelect', 'onCheck', 'selectable', 'checkable']);
const { onSelect, onCheck, keepChecked, singleType = false, selectable, checkable } = props;
const [data, setData] = useState([]);
const [{ pageIndex, pageSize }, setPagination] = useState({
pageIndex: 1,
pageSize: 100,
});
const [searchs, setSearchs] = useState('');
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true);
const ref = useRef();
const dataRef = useRef({
currentType: '',
preCheckedData: [],
preCheckedKeys: [],
preSelectedData: [],
flatData: {},
});
const { currentType, flatData } = dataRef.current;
const [checkedKeys, setCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [first, setFirst] = useState(true);
const requestQuery = useMemo(() => {
return {
deviceTypes,
pageIndex,
pageSize,
queryInfo: searchs,
getChild,
userID: userAccessor ? window?.globalConfig?.userInfo?.OID : void 0,
sortFields,
classField,
direction,
customerName,
};
}, [
customerName,
deviceTypes,
direction,
getChild,
pageIndex,
pageSize,
searchs,
sortFields,
classField,
]);
useEffect(() => {
setLoading(true);
getEquipmentInfo(requestQuery)
.then((res) => {
setLoading(false);
const { list, pageIndex, totalCount } = res.data || {};
const newData = pageIndex === 1 ? list : [...dataList, ...list]; // 非第一页时合并
// 是否需要默认选中
if (pageIndex === 1 && (!keepChecked || (keepChecked && !checkedKeys.length))) {
setCheckedKeys(newData.length > 0 ? [newData[0].code] : []);
onCheck?.(newData.length > 0 ? [newData[0]] : []);
// 仅单选时使用select相关数据
!checkable && onSelect?.(newData.length > 0 ? [newData[0]] : []);
!checkable && setSelectedKeys(newData.length > 0 ? [newData[0].code] : []);
singleType && (dataRef.current.currentType = newData[0].deviceType);
}
setData(() => {
dataList = [...newData];
return newData;
});
props.setDeviceList(newData);
props.setSearchStr(searchs);
setHasMore(newData.length < totalCount);
})
.catch((err) => {
setLoading(false);
});
}, [requestQuery]);
const onScroll = useCallback(() => {
if (loading || !hasMore) return;
const { clientHeight, scrollHeight, scrollTop } = ref.current;
if (clientHeight + scrollTop + 10 >= scrollHeight) {
// 加载更多
setPagination({
pageSize,
pageIndex: pageIndex + 1,
});
}
}, [loading, pageIndex, pageSize, hasMore]);
const onSearch = useCallback(
(value) => {
setSearchs(value);
setPagination({
pageIndex: 1,
pageSize,
});
},
[pageSize],
);
useEffect(() => {
ref.current?.addEventListener('scroll', onScroll, true);
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
ref.current?.removeEventListener('scroll', onScroll, true);
};
}, [onScroll]);
const treeData = useMemo(() => {
const treeData = treeDataGenerator(data, singleType && currentType, ref);
const flatData = flattenTreeData(treeData);
dataRef.current.flatData = Object.assign(dataRef.current.flatData || {}, flatData);
return convertToTree(treeData, requestQuery.classField ? 'deviceClass' : 'deviceType');
}, [data, singleType, currentType]);
const handleCheck = (keys, info) => {
// keys中会保留多余的key
const { checked } = keys;
setCheckedKeys(checked);
const data = checked.map((key) => flatData[key]);
onCheck?.(data);
singleType && data.length === 0 && (dataRef.current.currentType = '');
singleType && data.length === 1 && (dataRef.current.currentType = data[0].deviceType);
};
const handleSelect = (keys, info) => {
if (checkable) {
setCheckedKeys(keys);
const data = keys.map((key) => flatData[key]);
onCheck?.(data);
singleType && data.length === 0 && (dataRef.current.currentType = '');
singleType && data.length === 1 && (dataRef.current.currentType = data[0].deviceType);
} else {
setSelectedKeys(keys);
const data = keys.map((key) => flatData[key]);
onSelect?.(data);
}
};
return (
<div className={classNames(prefixCls, 'wkt-scroll-light')}>
<Input.Search placeholder="搜索设备名称" onSearch={onSearch} allowClear />
<div className={classnames(`${prefixCls}-tree-wrap`, 'wkt-scroll-light-plus')} ref={ref}>
<Spin spinning={loading}>
{data && data.length ? (
<Tree
checkStrictly
checkedKeys={checkedKeys}
selectedKeys={selectedKeys}
autoExpandParent
treeData={treeData}
defaultExpandParent
defaultExpandedKeys={expandedKeys}
{...cusProps}
onCheck={handleCheck}
onSelect={handleSelect}
/>
) : (
<PandaEmpty />
)}
</Spin>
</div>
</div>
);
};
DeviceTree.defaultProps = {
deviceTypes: '二供泵房',
userAccessor: false,
getChild: false,
sortFields: '',
direction: '',
customerName: '',
classField: '',
selectable: false,
checkable: false,
keepChecked: false,
onSelect: () => {},
onCheck: () => {},
setDeviceList: () => {},
setSearchStr: () => {},
};
DeviceTree.propTypes = {
deviceTypes: PropTypes.string,
userAccessor: PropTypes.bool,
getChild: PropTypes.bool,
sortFields: PropTypes.string,
direction: PropTypes.string,
customerName: PropTypes.string,
classField: PropTypes.string,
selectable: PropTypes.bool,
checkable: PropTypes.bool,
keepChecked: PropTypes.bool,
onSelect: PropTypes.func,
onCheck: PropTypes.func,
setDeviceList: PropTypes.func,
setSearchStr: PropTypes.func,
};
export default DeviceTree;
@root-entry-name: 'default';
@import '~antd/es/style/themes/index.less';
@ec-device-tree-prefix-cls: ~'@{ant-prefix}-ec-device-tree-group';
.@{ec-device-tree-prefix-cls} {
height: 100%;
padding: 10px;
overflow: hidden;
&-tree-wrap {
height: calc(100% - 48px);
overflow: auto;
}
:global {
.@{ant-prefix}-input-search {
margin-bottom: 16px;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment