@hzab/group-user-selector 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/ItemRenter/index.jsx +46 -0
- package/src/components/ItemRenter/index.less +33 -0
- package/src/components/SearchRenter/index.jsx +87 -0
- package/src/components/SearchRenter/index.less +36 -0
- package/src/{GroupSelection/index.less → index.less} +72 -69
- package/src/index.tsx +201 -3
- package/src/GroupSelection/index.jsx +0 -250
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import { Checkbox } from "antd";
|
|
3
|
+
|
|
4
|
+
import defaultAvatar from "../../assets/imgs/defaultAvatar.png";
|
|
5
|
+
import department from "../../assets/imgs/department.png";
|
|
6
|
+
|
|
7
|
+
import "./index.less";
|
|
8
|
+
|
|
9
|
+
function ItemRenter(props, ref) {
|
|
10
|
+
const {
|
|
11
|
+
labelKey = "label",
|
|
12
|
+
valueKey = "value",
|
|
13
|
+
isUserKey = "isUser",
|
|
14
|
+
imgKey = "img",
|
|
15
|
+
isCheck = false,
|
|
16
|
+
item,
|
|
17
|
+
handleClickItem,
|
|
18
|
+
handleCheckItem
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<div className="group-selection-list-item" key={item[valueKey]}>
|
|
25
|
+
<div className="avatar" onClick={() => handleCheckItem(item)}>
|
|
26
|
+
<Checkbox checked={isCheck} />
|
|
27
|
+
<img src={item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department} />
|
|
28
|
+
<div className="label">{item[labelKey]}</div>
|
|
29
|
+
</div>
|
|
30
|
+
{item[isUserKey] ? null : <div
|
|
31
|
+
className={`next ${isCheck ? "noNext" : ""}`}
|
|
32
|
+
onClick={() => {
|
|
33
|
+
if (isCheck) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
handleClickItem(item);
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
下级
|
|
40
|
+
</div>}
|
|
41
|
+
</div>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default forwardRef(ItemRenter);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.group-selection-list-item {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
margin-bottom: 24px;
|
|
6
|
+
|
|
7
|
+
.avatar {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
|
|
12
|
+
img {
|
|
13
|
+
width: 32px;
|
|
14
|
+
height: auto;
|
|
15
|
+
margin: 0 12px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.label {
|
|
19
|
+
font-size: 16px;
|
|
20
|
+
color: #141414;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.next {
|
|
25
|
+
color: rgb(64, 62, 245);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.noNext {
|
|
31
|
+
opacity: 0.5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useState, forwardRef } from "react";
|
|
2
|
+
import { Input,message, Pagination, } from "antd";
|
|
3
|
+
|
|
4
|
+
import "./index.less";
|
|
5
|
+
|
|
6
|
+
function SearchRenter(props, ref) {
|
|
7
|
+
const {
|
|
8
|
+
searchModel,
|
|
9
|
+
searchQueryKey = "search",
|
|
10
|
+
hasSearchPagination = true,
|
|
11
|
+
hasSearch = true,
|
|
12
|
+
ItemRenter,
|
|
13
|
+
setLoading = () => { },
|
|
14
|
+
} = props;
|
|
15
|
+
|
|
16
|
+
const [search, setSearch] = useState("");
|
|
17
|
+
const [searchList, setSearchList] = useState([]);
|
|
18
|
+
const [total, setTotal] = useState(0);
|
|
19
|
+
const [pageData, setPageData] = useState(hasSearchPagination ? {
|
|
20
|
+
pageSize: 10,
|
|
21
|
+
pageNum: 1
|
|
22
|
+
} : {});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
const handleSearch = (data) => {
|
|
27
|
+
setLoading && setLoading(true);
|
|
28
|
+
searchModel &&
|
|
29
|
+
searchModel.getList({ ...data }).then((res) => {
|
|
30
|
+
console.log(res, 2222222);
|
|
31
|
+
|
|
32
|
+
setSearchList(res?.list || res || []);
|
|
33
|
+
setTotal(res?.pagination?.total||res?.total || 0);
|
|
34
|
+
}).catch((err) => {
|
|
35
|
+
message.error(err._message || "未知错误");
|
|
36
|
+
})
|
|
37
|
+
.finally(() => {
|
|
38
|
+
setLoading && setLoading(false);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleChangePage = (pageNum, pageSize) => {
|
|
43
|
+
const pages = {
|
|
44
|
+
pageNum: pageSize !== pageData?.pageSize ? 1 : pageNum,
|
|
45
|
+
pageSize
|
|
46
|
+
}
|
|
47
|
+
setPageData({
|
|
48
|
+
...pages
|
|
49
|
+
})
|
|
50
|
+
handleSearch({
|
|
51
|
+
[searchQueryKey]: search,
|
|
52
|
+
...pages
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const onSearch = (value) => {
|
|
57
|
+
|
|
58
|
+
setSearch(value);
|
|
59
|
+
if (!value) return;
|
|
60
|
+
handleSearch({
|
|
61
|
+
[searchQueryKey]: value,
|
|
62
|
+
...pageData
|
|
63
|
+
})
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
{hasSearch ? <Input.Search onSearch={onSearch} enterButton placeholder="搜索成员" allowClear /> : null}
|
|
70
|
+
{search ? (
|
|
71
|
+
<div className="group-selection-search-view">
|
|
72
|
+
<div className="group-selection-search-list">
|
|
73
|
+
{searchList.map((item, index) => {
|
|
74
|
+
return ItemRenter(item);
|
|
75
|
+
})}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{hasSearchPagination ? <div className="group-selection-search-pagination">
|
|
79
|
+
<Pagination current={pageData?.pageNum} total={total} pageSize={pageData?.pageSize} onChange={handleChangePage} size="small" />
|
|
80
|
+
</div> : null}
|
|
81
|
+
</div>
|
|
82
|
+
) : null}
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default forwardRef(SearchRenter);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.group-selection-search-view {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
position: absolute;
|
|
6
|
+
top: 56px;
|
|
7
|
+
left: 24px;
|
|
8
|
+
width: calc(100% - 48px);
|
|
9
|
+
height: calc(100% - 80px);
|
|
10
|
+
overflow-y: hidden;
|
|
11
|
+
z-index: 10;
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
|
|
14
|
+
.group-selection-search-list {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
flex: 1;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
padding: 24px ;
|
|
19
|
+
|
|
20
|
+
&::-webkit-scrollbar {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.group-selection-search-pagination {
|
|
26
|
+
position: absolute;
|
|
27
|
+
left: 0;
|
|
28
|
+
bottom: 0;
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 40px;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
background-color: #fff;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -29,45 +29,48 @@
|
|
|
29
29
|
padding: 24px;
|
|
30
30
|
border-right: 1px solid #e8e8e8;
|
|
31
31
|
|
|
32
|
-
.group-selection-search-view {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
32
|
+
// .group-selection-search-view {
|
|
33
|
+
// display: flex;
|
|
34
|
+
// flex-direction: column;
|
|
35
|
+
// box-sizing: border-box;
|
|
36
|
+
// position: absolute;
|
|
37
|
+
// top: 56px;
|
|
38
|
+
// left: 24px;
|
|
39
|
+
// width: calc(100% - 48px);
|
|
40
|
+
// height: calc(100% - 80px);
|
|
41
|
+
// overflow-y: hidden;
|
|
42
|
+
// z-index: 10;
|
|
43
|
+
// background-color: #fff;
|
|
44
|
+
|
|
45
|
+
// .group-selection-search-list {
|
|
46
|
+
// box-sizing: border-box;
|
|
47
|
+
// flex: 1;
|
|
48
|
+
// overflow-y: auto;
|
|
49
|
+
// padding: 24px ;
|
|
50
|
+
|
|
51
|
+
// &::-webkit-scrollbar {
|
|
52
|
+
// display: none;
|
|
53
|
+
// }
|
|
54
|
+
// }
|
|
55
|
+
|
|
56
|
+
// .group-selection-search-pagination {
|
|
57
|
+
// position: absolute;
|
|
58
|
+
// left: 0;
|
|
59
|
+
// bottom: 0;
|
|
60
|
+
// width: 100%;
|
|
61
|
+
// height: 40px;
|
|
62
|
+
// display: flex;
|
|
63
|
+
// align-items: center;
|
|
64
|
+
// justify-content: center;
|
|
65
|
+
// background-color: #fff;
|
|
66
|
+
// }
|
|
67
|
+
// }
|
|
68
68
|
|
|
69
69
|
.group-selection-breadcrumb {
|
|
70
70
|
margin-top: 12px;
|
|
71
|
+
.group-selection-breadcrumb-item {
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
.group-selection-list-check {
|
|
@@ -83,39 +86,39 @@
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
.group-selection-list-item {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
89
|
+
// .group-selection-list-item {
|
|
90
|
+
// display: flex;
|
|
91
|
+
// align-items: center;
|
|
92
|
+
// justify-content: space-between;
|
|
93
|
+
// margin-bottom: 24px;
|
|
94
|
+
|
|
95
|
+
// .avatar {
|
|
96
|
+
// display: flex;
|
|
97
|
+
// align-items: center;
|
|
98
|
+
// cursor: pointer;
|
|
99
|
+
|
|
100
|
+
// img {
|
|
101
|
+
// width: 32px;
|
|
102
|
+
// height: auto;
|
|
103
|
+
// margin: 0 12px;
|
|
104
|
+
// }
|
|
105
|
+
|
|
106
|
+
// .label {
|
|
107
|
+
// font-size: 16px;
|
|
108
|
+
// color: #141414;
|
|
109
|
+
// }
|
|
110
|
+
// }
|
|
111
|
+
|
|
112
|
+
// .next {
|
|
113
|
+
// color: rgb(64, 62, 245);
|
|
114
|
+
// cursor: pointer;
|
|
115
|
+
// flex-shrink: 0;
|
|
116
|
+
// }
|
|
117
|
+
|
|
118
|
+
// .noNext {
|
|
119
|
+
// opacity: 0.5;
|
|
120
|
+
// }
|
|
121
|
+
// }
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
.group-selection-detail {
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { useEffect, useState, useImperativeHandle, forwardRef } from "react";
|
|
2
|
+
import { Input, Breadcrumb, Checkbox, message, Spin, Pagination, ConfigProvider } from "antd";
|
|
3
|
+
import zhCN from 'antd/es/locale/zh_CN';
|
|
4
|
+
import { CloseCircleFilled, SearchOutlined } from "@ant-design/icons";
|
|
5
|
+
import 'moment/locale/zh-cn';
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
|
|
8
|
+
import defaultAvatar from "./assets/imgs/defaultAvatar.png";
|
|
9
|
+
import department from "./assets/imgs/department.png";
|
|
10
|
+
import SearchRenter from './components/SearchRenter'
|
|
11
|
+
import ItemRenter from './components/ItemRenter'
|
|
12
|
+
|
|
13
|
+
import "./index.less";
|
|
14
|
+
|
|
15
|
+
function GroupSelection(props, ref) {
|
|
16
|
+
const {
|
|
17
|
+
model,
|
|
18
|
+
queryKey = "parentId",
|
|
19
|
+
value = [],
|
|
20
|
+
labelKey = "label",
|
|
21
|
+
valueKey = "value",
|
|
22
|
+
isUserKey = "isUser",
|
|
23
|
+
imgKey = "img",
|
|
24
|
+
hasPagination = true,
|
|
25
|
+
} = props;
|
|
26
|
+
const [breadcrumb, setBreadcrumb] = useState([
|
|
27
|
+
{
|
|
28
|
+
[labelKey]: "首页",
|
|
29
|
+
[valueKey]: undefined,
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
const [loading, setLoading] = useState(false);
|
|
33
|
+
const [checkAll, setCheckAll] = useState(false);
|
|
34
|
+
const [selectList, setSelectList] = useState([]);
|
|
35
|
+
const [selectedList, setSelectedList] = useState([...value]);
|
|
36
|
+
const [queryValue, setQueryValue] = useState(undefined);
|
|
37
|
+
const [total, setTotal] = useState(0);
|
|
38
|
+
const [pageData, setPageData] = useState(hasPagination ? {
|
|
39
|
+
pageSize: 10,
|
|
40
|
+
pageNum: 1
|
|
41
|
+
} : {});
|
|
42
|
+
|
|
43
|
+
// 通过useImperativeHandle定义要抛出的值或方法
|
|
44
|
+
useImperativeHandle(ref, () => ({
|
|
45
|
+
selectedList,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const selectedValues = new Set(selectedList.map((item) => item[valueKey]));
|
|
50
|
+
const isAllSelected = selectList.every((item) => selectedValues.has(item[valueKey]));
|
|
51
|
+
setCheckAll(isAllSelected);
|
|
52
|
+
}, [selectList, selectedList]);
|
|
53
|
+
|
|
54
|
+
const getTreeData = (value?: string | number) => {
|
|
55
|
+
setLoading(true);
|
|
56
|
+
model &&
|
|
57
|
+
model.getList({ [queryKey]: value || queryValue, ...pageData }).then((res) => {
|
|
58
|
+
setSelectList(res?.list || res || []);
|
|
59
|
+
setTotal(res?.pagination?.total||res?.total || 0);
|
|
60
|
+
})
|
|
61
|
+
.catch((err) => {
|
|
62
|
+
message.error(err._message || "未知错误");
|
|
63
|
+
})
|
|
64
|
+
.finally(() => {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
getTreeData();
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const handleChangePage = (pageNum, pageSize) => {
|
|
76
|
+
const pages = {
|
|
77
|
+
pageNum: pageSize !== pageData?.pageSize ? 1 : pageNum,
|
|
78
|
+
pageSize
|
|
79
|
+
}
|
|
80
|
+
setPageData({
|
|
81
|
+
...pages
|
|
82
|
+
})
|
|
83
|
+
getTreeData()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
const handleBreadcrumb = (e) => {
|
|
89
|
+
const index = breadcrumb.findIndex((item) => item[valueKey] === e[valueKey]);
|
|
90
|
+
const arr = [...breadcrumb];
|
|
91
|
+
arr.splice(index + 1);
|
|
92
|
+
setBreadcrumb(arr);
|
|
93
|
+
setQueryValue(e[valueKey]);
|
|
94
|
+
getTreeData(e[valueKey]);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleClickItem = (item) => {
|
|
98
|
+
const arr = [...breadcrumb];
|
|
99
|
+
arr.push(item);
|
|
100
|
+
setBreadcrumb(arr);
|
|
101
|
+
setQueryValue(item[valueKey]);
|
|
102
|
+
getTreeData(item[valueKey]);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleCheckDelete = (item) => {
|
|
106
|
+
setSelectedList(selectedList.filter((i) => i[valueKey] !== item[valueKey]));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleCheckItem = (item) => {
|
|
110
|
+
const is = selectedList.some((i) => i[valueKey] === item[valueKey]);
|
|
111
|
+
if (is) {
|
|
112
|
+
handleCheckDelete(item);
|
|
113
|
+
} else {
|
|
114
|
+
setSelectedList([...selectedList, item]);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handleCheckAll = (e) => {
|
|
119
|
+
const isChecked = e.target.checked;
|
|
120
|
+
if (isChecked) {
|
|
121
|
+
// 全选:合并 selectList 并去重
|
|
122
|
+
setSelectedList((prev) => {
|
|
123
|
+
const selectedValues = new Set(prev.map((item) => item[valueKey])); // 已选值的 Set
|
|
124
|
+
const newItems = selectList.filter((item) => !selectedValues.has(item[valueKey]));
|
|
125
|
+
return [...prev, ...newItems]; // 合并新项
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
// 取消全选:仅移除 selectList 中的项
|
|
129
|
+
setSelectedList((prev) => {
|
|
130
|
+
const selectValues = new Set(selectList.map((item) => item[valueKey])); // 需移除值的 Set
|
|
131
|
+
return prev.filter((item) => !selectValues.has(item[valueKey])); // 过滤保留其他项
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const handleCheck = (item) => {
|
|
137
|
+
const is = selectedList.some((i) => i[valueKey] === item[valueKey]);
|
|
138
|
+
return is;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const Renter = (item) => {
|
|
142
|
+
const isCheck = handleCheck(item);
|
|
143
|
+
return (
|
|
144
|
+
<ItemRenter
|
|
145
|
+
{...props}
|
|
146
|
+
item={item}
|
|
147
|
+
isCheck={isCheck}
|
|
148
|
+
handleCheckItem={handleCheckItem}
|
|
149
|
+
handleCheckDelete={handleCheckDelete}
|
|
150
|
+
handleClickItem={handleClickItem}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<ConfigProvider locale={zhCN}>
|
|
157
|
+
|
|
158
|
+
<div className="group-selection">
|
|
159
|
+
{loading ? <div className="group-selection-loading">
|
|
160
|
+
<Spin tip="Loading..." spinning={loading} />
|
|
161
|
+
</div> : null}
|
|
162
|
+
<div className="group-selection-list">
|
|
163
|
+
<SearchRenter {...props} setLoading={setLoading} ItemRenter={Renter} />
|
|
164
|
+
<Breadcrumb className="group-selection-breadcrumb">
|
|
165
|
+
{breadcrumb.map((item) => {
|
|
166
|
+
return (
|
|
167
|
+
<Breadcrumb.Item className="group-selection-breadcrumb-item" onClick={() => handleBreadcrumb(item)} key={item[valueKey]}>
|
|
168
|
+
{item[labelKey]}
|
|
169
|
+
</Breadcrumb.Item>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
</Breadcrumb>
|
|
173
|
+
<div className="group-selection-list-check">
|
|
174
|
+
<Checkbox onChange={handleCheckAll} checked={checkAll && !!selectList.length} /> 全选
|
|
175
|
+
</div>
|
|
176
|
+
<div className="group-selection-list-view">
|
|
177
|
+
{selectList.map((item, index) => {
|
|
178
|
+
return Renter(item);
|
|
179
|
+
})}
|
|
180
|
+
</div>
|
|
181
|
+
{hasPagination ? <div>
|
|
182
|
+
<Pagination current={pageData?.pageNum} total={total} pageSize={pageData?.pageSize} onChange={handleChangePage} size="small" />
|
|
183
|
+
</div> : null}
|
|
184
|
+
</div>
|
|
185
|
+
<div className="group-selection-detail">
|
|
186
|
+
<div className="group-selection-detail-list">
|
|
187
|
+
{selectedList.map((item, index) => {
|
|
188
|
+
return (
|
|
189
|
+
<div className="group-selection-detail-item" key={item[valueKey]}>
|
|
190
|
+
<img src={item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department} />
|
|
191
|
+
<div className="label">{item[labelKey]}</div>
|
|
192
|
+
<CloseCircleFilled style={{ cursor: "pointer" }} onClick={() => handleCheckDelete(item)} />
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
})}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</ConfigProvider>
|
|
200
|
+
);
|
|
3
201
|
}
|
|
4
202
|
|
|
5
|
-
export default
|
|
203
|
+
export default forwardRef(GroupSelection);
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState, useImperativeHandle, forwardRef } from "react";
|
|
2
|
-
import { Input, Breadcrumb, Checkbox, message, Spin, Pagination, ConfigProvider } from "antd";
|
|
3
|
-
import zhCN from 'antd/es/locale/zh_CN';
|
|
4
|
-
import { CloseCircleFilled, SearchOutlined } from "@ant-design/icons";
|
|
5
|
-
import 'moment/locale/zh-cn';
|
|
6
|
-
import _ from "lodash";
|
|
7
|
-
import defaultAvatar from "../assets/imgs/defaultAvatar.png";
|
|
8
|
-
import department from "../assets/imgs/department.png";
|
|
9
|
-
|
|
10
|
-
import "./index.less";
|
|
11
|
-
|
|
12
|
-
function GroupSelection(props, ref) {
|
|
13
|
-
const {
|
|
14
|
-
model,
|
|
15
|
-
searchModel,
|
|
16
|
-
queryKey = "parentId",
|
|
17
|
-
searchQueryKey = "search",
|
|
18
|
-
value = [],
|
|
19
|
-
labelKey = "label",
|
|
20
|
-
valueKey = "value",
|
|
21
|
-
isUserKey = "isUser",
|
|
22
|
-
imgKey = "img",
|
|
23
|
-
hasPagination = true,
|
|
24
|
-
hasSearch = true
|
|
25
|
-
} = props;
|
|
26
|
-
const [breadcrumb, setBreadcrumb] = useState([
|
|
27
|
-
{
|
|
28
|
-
[labelKey]: "首页",
|
|
29
|
-
[valueKey]: undefined,
|
|
30
|
-
},
|
|
31
|
-
]);
|
|
32
|
-
const [loading, setLoading] = useState(false);
|
|
33
|
-
const [checkAll, setCheckAll] = useState(false);
|
|
34
|
-
const [selectList, setSelectList] = useState([]);
|
|
35
|
-
const [selectedList, setSelectedList] = useState([...value]);
|
|
36
|
-
const [search, setSearch] = useState("");
|
|
37
|
-
const [searchList, setSearchList] = useState([]);
|
|
38
|
-
const [total, setTotal] = useState(0);
|
|
39
|
-
const [pageData, setPageData] = useState(hasPagination ? {
|
|
40
|
-
pageSize: 10,
|
|
41
|
-
pageNum: 1
|
|
42
|
-
} : {});
|
|
43
|
-
|
|
44
|
-
// 通过useImperativeHandle定义要抛出的值或方法
|
|
45
|
-
useImperativeHandle(ref, () => ({
|
|
46
|
-
selectedList,
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const selectedValues = new Set(selectedList.map((item) => item[valueKey]));
|
|
51
|
-
const isAllSelected = selectList.every((item) => selectedValues.has(item[valueKey]));
|
|
52
|
-
setCheckAll(isAllSelected);
|
|
53
|
-
}, [selectList, selectedList]);
|
|
54
|
-
|
|
55
|
-
const getTreeData = (value) => {
|
|
56
|
-
setLoading(true);
|
|
57
|
-
model &&
|
|
58
|
-
model.get({ [queryKey]: value }).then((res) => {
|
|
59
|
-
setSelectList(res);
|
|
60
|
-
})
|
|
61
|
-
.catch((err) => {
|
|
62
|
-
message.error(err._message || "未知错误");
|
|
63
|
-
})
|
|
64
|
-
.finally(() => {
|
|
65
|
-
setLoading(false);
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
getTreeData();
|
|
72
|
-
}, []);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const handleSearch = (data) => {
|
|
76
|
-
setLoading(true);
|
|
77
|
-
searchModel &&
|
|
78
|
-
searchModel.get({ ...data }).then((res) => {
|
|
79
|
-
const list = res?.list || [];
|
|
80
|
-
setSearchList(list);
|
|
81
|
-
setTotal(res?.total);
|
|
82
|
-
}).catch((err) => {
|
|
83
|
-
message.error(err._message || "未知错误");
|
|
84
|
-
})
|
|
85
|
-
.finally(() => {
|
|
86
|
-
setLoading(false);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const handleChangePage = (pageNum, pageSize) => {
|
|
91
|
-
const pages = {
|
|
92
|
-
pageNum: pageSize !== pageData?.pageSize ? 1 : pageNum,
|
|
93
|
-
pageSize
|
|
94
|
-
}
|
|
95
|
-
setPageData({
|
|
96
|
-
...pages
|
|
97
|
-
})
|
|
98
|
-
handleSearch({
|
|
99
|
-
[searchQueryKey]: search,
|
|
100
|
-
...pages
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const onSearch = (value) => {
|
|
105
|
-
|
|
106
|
-
setSearch(value);
|
|
107
|
-
if (!value) return;
|
|
108
|
-
handleSearch({
|
|
109
|
-
[searchQueryKey]: value,
|
|
110
|
-
...pageData
|
|
111
|
-
})
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const handleBreadcrumb = (e) => {
|
|
115
|
-
const index = breadcrumb.findIndex((item) => item[valueKey] === e[valueKey]);
|
|
116
|
-
const arr = [...breadcrumb];
|
|
117
|
-
arr.splice(index + 1);
|
|
118
|
-
setBreadcrumb(arr);
|
|
119
|
-
getTreeData(e[valueKey]);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const handleClickItem = (item) => {
|
|
123
|
-
const arr = [...breadcrumb];
|
|
124
|
-
arr.push(item);
|
|
125
|
-
setBreadcrumb(arr);
|
|
126
|
-
|
|
127
|
-
getTreeData(item[valueKey]);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const handleCheckDelete = (item) => {
|
|
131
|
-
setSelectedList(selectedList.filter((i) => i[valueKey] !== item[valueKey]));
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const handleCheckItem = (item) => {
|
|
135
|
-
const is = selectedList.some((i) => i[valueKey] === item[valueKey]);
|
|
136
|
-
if (is) {
|
|
137
|
-
handleCheckDelete(item);
|
|
138
|
-
} else {
|
|
139
|
-
setSelectedList([...selectedList, item]);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const handleCheckAll = (e) => {
|
|
144
|
-
const isChecked = e.target.checked;
|
|
145
|
-
if (isChecked) {
|
|
146
|
-
// 全选:合并 selectList 并去重(基于 value)
|
|
147
|
-
setSelectedList((prev) => {
|
|
148
|
-
const selectedValues = new Set(prev.map((item) => item[valueKey])); // 已选值的 Set
|
|
149
|
-
const newItems = selectList.filter((item) => !selectedValues.has(item[valueKey]));
|
|
150
|
-
return [...prev, ...newItems]; // 合并新项
|
|
151
|
-
});
|
|
152
|
-
} else {
|
|
153
|
-
// 取消全选:仅移除 selectList 中的项(基于 value)
|
|
154
|
-
setSelectedList((prev) => {
|
|
155
|
-
const selectValues = new Set(selectList.map((item) => item[valueKey])); // 需移除值的 Set
|
|
156
|
-
return prev.filter((item) => !selectValues.has(item[valueKey])); // 过滤保留其他项
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const handleCheck = (item) => {
|
|
162
|
-
const is = selectedList.some((i) => i[valueKey] === item[valueKey]);
|
|
163
|
-
return is;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const ItemRenter = (item) => {
|
|
167
|
-
const isCheck = handleCheck(item);
|
|
168
|
-
return (
|
|
169
|
-
<div className="group-selection-list-item" key={item[valueKey]}>
|
|
170
|
-
<div className="avatar" onClick={() => handleCheckItem(item)}>
|
|
171
|
-
<Checkbox checked={isCheck} />
|
|
172
|
-
<img src={item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department} />
|
|
173
|
-
<div className="label">{item[labelKey]}</div>
|
|
174
|
-
</div>
|
|
175
|
-
{item[isUserKey] ? null : <div
|
|
176
|
-
className={`next ${isCheck ? "noNext" : ""}`}
|
|
177
|
-
onClick={() => {
|
|
178
|
-
if (isCheck) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
handleClickItem(item);
|
|
182
|
-
}}
|
|
183
|
-
>
|
|
184
|
-
下级
|
|
185
|
-
</div>}
|
|
186
|
-
</div>
|
|
187
|
-
);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<ConfigProvider locale={zhCN}>
|
|
192
|
-
|
|
193
|
-
<div className="group-selection">
|
|
194
|
-
{loading ? <div className="group-selection-loading">
|
|
195
|
-
<Spin tip="Loading..." spinning={loading} />
|
|
196
|
-
</div> : null}
|
|
197
|
-
<div className="group-selection-list">
|
|
198
|
-
{hasSearch ? <Input.Search onSearch={onSearch} enterButton placeholder="搜索成员" allowClear /> : null}
|
|
199
|
-
|
|
200
|
-
{/* <Input addonBefore={<SearchOutlined />} allowClear placeholder="搜索成员" onChange={onSearch} /> */}
|
|
201
|
-
{search ? (
|
|
202
|
-
<div className="group-selection-search-view">
|
|
203
|
-
<div className="group-selection-search-list">
|
|
204
|
-
{searchList.map((item, index) => {
|
|
205
|
-
return ItemRenter(item);
|
|
206
|
-
})}
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
{hasPagination ? <div className="group-selection-search-pagination">
|
|
210
|
-
<Pagination current={pageData?.pageNum} total={total} pageSize={pageData?.pageSize} onChange={handleChangePage} size="small" />
|
|
211
|
-
</div> : null}
|
|
212
|
-
</div>
|
|
213
|
-
) : null}
|
|
214
|
-
<Breadcrumb className="group-selection-breadcrumb">
|
|
215
|
-
{breadcrumb.map((item) => {
|
|
216
|
-
return (
|
|
217
|
-
<Breadcrumb.Item style={{ cursor: "pointer" }} onClick={() => handleBreadcrumb(item)} key={item[valueKey]}>
|
|
218
|
-
{item[labelKey]}
|
|
219
|
-
</Breadcrumb.Item>
|
|
220
|
-
);
|
|
221
|
-
})}
|
|
222
|
-
</Breadcrumb>
|
|
223
|
-
<div className="group-selection-list-check">
|
|
224
|
-
<Checkbox onChange={handleCheckAll} checked={checkAll && selectList.length} /> 全选
|
|
225
|
-
</div>
|
|
226
|
-
<div className="group-selection-list-view">
|
|
227
|
-
{selectList.map((item, index) => {
|
|
228
|
-
return ItemRenter(item);
|
|
229
|
-
})}
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
<div className="group-selection-detail">
|
|
233
|
-
<div className="group-selection-detail-list">
|
|
234
|
-
{selectedList.map((item, index) => {
|
|
235
|
-
return (
|
|
236
|
-
<div className="group-selection-detail-item" key={item[valueKey]}>
|
|
237
|
-
<img src={item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department} />
|
|
238
|
-
<div className="label">{item[labelKey]}</div>
|
|
239
|
-
<CloseCircleFilled style={{ cursor: "pointer" }} onClick={() => handleCheckDelete(item)} />
|
|
240
|
-
</div>
|
|
241
|
-
);
|
|
242
|
-
})}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
</ConfigProvider>
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export default forwardRef(GroupSelection);
|