Commit 87aa3c77 authored by 涂茜's avatar 涂茜

feat: 指标选择组件

parent 5e0b9e87
Pipeline #24427 failed with stages
in 1 minute 49 seconds
......@@ -99,7 +99,7 @@ export default {
},
{
title: '通用',
children: ['ImageSelect'],
children: ['ImageSelect', 'QuotaSelect'],
},
{
title: '数据录入',
......
# `@wisdom-components/QuotaSelect`
> TODO: description
## Usage
```
const quotaselect = require('@wisdom-components/QuotaSelect');
// TODO: DEMONSTRATE API
```
{
"name": "@wisdom-components/quotaselect",
"version": "1.0.1",
"description": "> TODO: description",
"author": "tuqian <webtuqian@163.com>",
"homepage": "",
"license": "ISC",
"main": "lib/index.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"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"
}
}
---
title: QuotaSelect - 指标选择
nav:
title: 组件
path: /components
group:
path: /
---
# QuotaSelect 指标选择
基础业务组件
- 允许对分类指标进行选择
- 允许限制选择最大指标数
- 允许搜索指标
## 何时使用
- 在指标选择时。
## 代码演示
<code src="./demos/Basic.js">
## API
指标选择按钮 props 参考 https://ant.design/components/button-cn/#API
已选指标树 props 参考 https://ant.design/components/tree-cn/#API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| buttonProps | 指标选择按钮 | object | { } |
| width | 模态框宽度 | number | 900 |
| title | 模态框标题 | string | 选择显示字段 |
| cancelText | 模态框取消文本 | string | 取消选择 |
| placeholder | 搜索框占位符 | string | 搜索关键词 |
| searchPrefix | 搜索框前置图标 | ReactNode | SearchOutlined |
| targetValue | 指标类型 | string | 重要指标 |
| maximum | 最多可选择指标数量 | number | 0 |
| dataSource | 指标列表数据源 | array | [ ] |
| selectData | 已选指标数据 | array | [ ] |
| treeProps | 已选指标树 props | object | { } |
| onModalCancel | 点击模态框取消按钮的回调 | function(value){ } | - |
| onModalOk | 模态框点击确定回调 | function(value){ } | - |
| onSearch | 搜索框输入事件的回调,会返回搜索框输入信息 | function(value){ } | - |
| onRadioChange | 指标类型切换的回调 | function(value){ } | - |
| onCancelSelect | 点击已选指标树的删除按钮的回调 | function(value){ } | - |
import React, { useEffect, useState } from 'react';
import request from 'umi-request';
import { UnorderedListOutlined, SearchOutlined } from '@ant-design/icons';
import PandaQuotaSelect from '../index';
const Demo = () => {
const [quotaList, setQuotaList] = useState([]);
const [selectData, setSelectData] = useState([]);
const fetchData = () => {
request(baseUrl + '/Publish/Monitor/Device/GetQuotaList', {
method: 'get',
params: {
accountName: '二供泵房',
addrSchemeID: '',
},
})
.then(function (response) {
if (response.code == '0') {
let curData = response.data.map((item) => ({
title: item.Name,
key: item.Name,
checked: false,
...item,
}));
setQuotaList(curData);
}
})
.catch(function (error) {
console.log(error);
});
};
useEffect(() => {
fetchData();
}, []);
const onModalOk = () => {
console.log('onModalOk');
};
const onModalCancel = () => {
let newQuotaList = [...quotaList];
newQuotaList.forEach((item) => {
item.checked = false;
});
setQuotaList(newQuotaList);
setSelectData([]);
console.log('onModalCancel');
};
const onCheckboxChange = (e) => {
let curData = [...selectData];
let newQuotaList = [...quotaList];
let data = curData.filter((item) => item.title === e.target.value);
if (data.length) {
curData = curData.filter((item) => item.title !== e.target.value);
} else {
curData.push({ key: e.target.value, title: e.target.value });
}
newQuotaList.forEach((item) => {
if (item.title === e.target.value) item.checked = !item.checked;
});
setSelectData(curData);
setQuotaList(newQuotaList);
console.log(e.target.value, 'onCheckboxChange');
};
const onSearch = (e) => {
if (e.type === 'keydown') {
console.log(e.target.value, 'onSearch');
}
};
const onRadioChange = (e) => {
console.log(e, 'onRadioChange');
};
const onCancelSelect = ({ title }) => {
let curData = [...selectData];
let newQuotaList = [...quotaList];
let data = curData.filter((item) => item.title !== title);
newQuotaList.forEach((item) => {
if (item.title === title) item.checked = !item.checked;
});
setSelectData(data);
setQuotaList(newQuotaList);
console.log(title, 'onCancelSelect');
};
const onDragEnter = (info) => {
console.log(info, 'onDragEnter');
};
const onDrop = (info) => {
console.log(info, 'onDrop');
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data, key, callback) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].children) {
loop(data[i].children, key, callback);
}
}
};
const data = [...selectData];
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
loop(data, dropKey, (item) => {
item.children = item.children || [];
item.children.unshift(dragObj);
});
} else if (
(info.node.props.children || []).length > 0 && // Has children
info.node.props.expanded && // Is expanded
dropPosition === 1 // On the bottom gap
) {
loop(data, dropKey, (item) => {
item.children = item.children || [];
item.children.unshift(dragObj);
});
} else {
let ar;
let i;
loop(data, dropKey, (item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
setSelectData(data);
};
return (
<PandaQuotaSelect
buttonProps={{
icon: <UnorderedListOutlined />,
children: `指标选择(${selectData.length})`,
}}
onModalCancel={onModalCancel}
onModalOk={onModalOk}
maximum={3}
targetValue={'emphasis'}
dataSource={quotaList}
selectData={selectData}
onSearch={onSearch}
searchPrefix={<SearchOutlined />}
onRadioChange={onRadioChange}
onCheckboxChange={onCheckboxChange}
onCancelSelect={onCancelSelect}
treeProps={{
draggable: true,
onDragEnter,
onDrop,
}}
/>
);
};
export default Demo;
const baseUrl = 'http://192.168.10.150:8777';
import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Button, Input, Radio, Modal, Checkbox, Row, Col, Tree, ConfigProvider } from 'antd';
import Empty from '@wisdom-components/Empty';
import { ExclamationCircleFilled, SearchOutlined, CloseOutlined } from '@ant-design/icons';
import './index.less';
const { TreeNode } = Tree;
const QuotaSelect = ({
buttonProps,
width,
title,
cancelText,
onModalOk,
onModalCancel,
searchPrefix,
placeholder,
targetValue,
maximum,
dataSource,
selectData,
onSearch,
onRadioChange,
onCheckboxChange,
onCancelSelect,
treeProps,
}) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('quota-select');
const [visible, setVisible] = useState(false);
// 展示模态框
const showModal = () => {
setVisible(true);
};
// 关闭模态框
const handleCancel = (e) => {
if (e.target.innerHTML === cancelText) {
onModalCancel && onModalCancel();
} else {
setVisible(false);
}
};
const onOk = () => {
onModalOk && onModalOk();
setVisible(false);
};
return (
<div className={classNames(prefixCls)}>
<Button {...buttonProps} onClick={showModal} />
<Modal
centered
width={width}
title={title}
cancelText={cancelText}
visible={visible}
onCancel={handleCancel}
onOk={onOk}
className={classNames(`${prefixCls}-modal`)}
>
<div className={classNames(`${prefixCls}-modal-wrap`)}>
<div className={classNames(`${prefixCls}-modal-left`)}>
<div className={classNames(`${prefixCls}-modal-select-wrap`)}>
<Input
className={classNames(`${prefixCls}-modal-search`)}
placeholder={placeholder}
bordered={false}
prefix={searchPrefix}
onChange={onSearch}
onPressEnter={onSearch}
/>
<div className={classNames(`${prefixCls}-modal-target`)}>
<div>指标:</div>
<Radio.Group onChange={onRadioChange} defaultValue={targetValue}>
<Radio.Button value="emphasis">重点指标</Radio.Button>
<Radio.Button value="all">全部</Radio.Button>
</Radio.Group>
</div>
<div
className={classNames(`${prefixCls}-modal-select`, {
warning: !(selectData.length < maximum),
})}
>
{selectData.length < maximum ? (
<>
<ExclamationCircleFilled />
<div>已选择 {selectData.length} 个指标</div>
</>
) : (
<>
<ExclamationCircleFilled />
<div>已达上限,最多选择 {maximum} 个指标</div>
</>
)}
</div>
</div>
<div className={classNames(`${prefixCls}-modal-option-wrap`)}>
{!dataSource.length && <Empty />}
<Row gutter={[0, 6]}>
{!!dataSource.length &&
dataSource.map((item) => (
<Col span={8} key={item.key}>
<Checkbox
value={item.title}
checked={item.checked}
disabled={
(selectData.length > maximum || selectData.length === maximum) &&
!item.checked
}
onChange={onCheckboxChange}
>
{item.title}
</Checkbox>
</Col>
))}
</Row>
</div>
</div>
<div className={classNames(`${prefixCls}-modal-right`)}>
<div className={classNames(`${prefixCls}-modal-number`)}>
已选:{selectData.length}/{maximum}
</div>
<div className={classNames(`${prefixCls}-modal-tree`)}>
<Tree {...treeProps}>
{selectData.map((item) => (
<TreeNode
key={item.key}
title={
<div className={classNames(`${prefixCls}-modal-tree-title`)}>
<div>{item.title}</div>
<CloseOutlined onClick={() => onCancelSelect(item)} />
</div>
}
/>
))}
</Tree>
</div>
</div>
</div>
</Modal>
</div>
);
};
QuotaSelect.defaultProps = {
buttonProps: {},
width: 900,
title: '选择显示字段',
cancelText: '取消选择',
placeholder: '搜索关键词',
searchPrefix: <SearchOutlined />,
targetValue: 'emphasis',
maximum: 0,
dataSource: [],
selectData: [],
treeProps: {},
onModalCancel: () => {},
onModalOk: () => {},
onSearch: () => {},
onRadioChange: () => {},
onCancelSelect: () => {},
};
QuotaSelect.propTypes = {
buttonProps: PropTypes.object,
width: PropTypes.number,
title: PropTypes.string,
cancelText: PropTypes.string,
placeholder: PropTypes.string,
searchPrefix: PropTypes.node,
targetValue: PropTypes.string,
maximum: PropTypes.number,
dataSource: PropTypes.array,
selectData: PropTypes.array,
treeProps: PropTypes.object,
onModalCancel: PropTypes.func,
onModalOk: PropTypes.func,
onSearch: PropTypes.func,
onRadioChange: PropTypes.func,
onCancelSelect: PropTypes.func,
};
export default QuotaSelect;
@import (reference) '~antd/es/style/themes/default';
@quota-select-prefix-cls: ~'@{ant-prefix}-quota-select';
.@{quota-select-prefix-cls} {
}
.@{quota-select-prefix-cls}-modal {
&-wrap {
display: flex;
height: 500px;
}
&-left {
flex: 7;
border-right: 1px solid rgba(0, 0, 0, 0.2);
.empty {
margin: 128px 0;
}
}
&-right {
flex: 3;
}
&-select-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px 0 0;
}
&-search {
width: 160px;
background-color: #eef1f9;
border-radius: 15px;
.ant-input-prefix {
color: #5a6c8a;
}
}
&-search:hover {
background-color: #eef1f9;
}
&-target {
display: flex;
align-items: center;
white-space: nowrap;
margin: 0 10px 0 10px;
}
&-select {
display: flex;
align-items: center;
white-space: nowrap;
span {
margin-right: 6px;
font-size: 16px;
color: #52c41a;
}
}
&-select.warning {
color: #f0495d;
span {
color: #f0495d;
}
}
&-option-wrap {
height: calc(100% - 52px);
margin-top: 20px;
overflow-y: auto;
}
&-number {
margin-bottom: 10px;
padding: 4px 0 10px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
}
&-tree {
height: calc(100% - 47px);
overflow-y: auto;
.ant-tree-treenode,
.ant-tree-node-content-wrapper {
width: 100%;
}
}
&-tree-title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
}
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