@hzab/group-user-selector 0.0.10 → 0.0.11-alpha.0
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/CHANGELOG.md +8 -0
- package/README.md +97 -90
- package/package.json +1 -1
- package/src/assets/imgs/department.png +0 -0
- package/src/assets/imgs/emptyIcon.png +0 -0
- package/src/assets/imgs/searchIcon.png +0 -0
- package/src/components/ItemRenter/index.jsx +44 -18
- package/src/components/ItemRenter/index.less +57 -33
- package/src/components/SearchRenter/index.jsx +12 -1
- package/src/components/SearchRenter/index.less +51 -36
- package/src/index.less +84 -28
- package/src/index.tsx +91 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
# @hzab/group-user-selector@0.1.0
|
|
2
|
+
|
|
3
|
+
feat: 新增 iconCustomFunc 自定义返回图片函数
|
|
4
|
+
feat: 新增 LableSlot 插槽
|
|
5
|
+
feat: 新增 ItemRender 列表 item 自定义渲染
|
|
6
|
+
feat: 新增 SelectItemRender 选中 item 自定义渲染
|
|
7
|
+
feat: 新增 modal 计算选中人数模式
|
|
8
|
+
|
|
1
9
|
# @hzab/group-user-selector@0.0.10
|
|
2
10
|
|
|
3
11
|
feat: 搜索情况下默认不展示下级
|
package/README.md
CHANGED
|
@@ -1,90 +1,97 @@
|
|
|
1
|
-
# @hzab/group-user-selector
|
|
2
|
-
|
|
3
|
-
组件模板
|
|
4
|
-
|
|
5
|
-
- node@16.16.0
|
|
6
|
-
|
|
7
|
-
- 注意:首次克隆先执行 npm run prepare 命令
|
|
8
|
-
|
|
9
|
-
# 组件
|
|
10
|
-
|
|
11
|
-
## 示例
|
|
12
|
-
|
|
13
|
-
```jsx
|
|
14
|
-
import Demo from "@hzab/group-user-selector";
|
|
15
|
-
|
|
16
|
-
<Demo />;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## API
|
|
20
|
-
|
|
21
|
-
### InfoPanel Attributes
|
|
22
|
-
|
|
23
|
-
| 参数 | 类型
|
|
24
|
-
| ------------------- |
|
|
25
|
-
| value | Object
|
|
26
|
-
| model | Object
|
|
27
|
-
| modelGetType | string
|
|
28
|
-
| searchModel | Object
|
|
29
|
-
| queryKey | string
|
|
30
|
-
| searchQueryKey | string
|
|
31
|
-
| labelKey | string
|
|
32
|
-
| valueKey | string
|
|
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
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
npm
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
# @hzab/group-user-selector
|
|
2
|
+
|
|
3
|
+
组件模板
|
|
4
|
+
|
|
5
|
+
- node@16.16.0
|
|
6
|
+
|
|
7
|
+
- 注意:首次克隆先执行 npm run prepare 命令
|
|
8
|
+
|
|
9
|
+
# 组件
|
|
10
|
+
|
|
11
|
+
## 示例
|
|
12
|
+
|
|
13
|
+
```jsx
|
|
14
|
+
import Demo from "@hzab/group-user-selector";
|
|
15
|
+
|
|
16
|
+
<Demo />;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## API
|
|
20
|
+
|
|
21
|
+
### InfoPanel Attributes
|
|
22
|
+
|
|
23
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
24
|
+
| ------------------- | -------- | ---- | ----------- | ----------------------------------- |
|
|
25
|
+
| value | Object | 否 | [] | 选中的人员数据 |
|
|
26
|
+
| model | Object | 否 | - | 列表的请求 model (getListApi) |
|
|
27
|
+
| modelGetType | string | 否 | getList | 列表的请求类型 |
|
|
28
|
+
| searchModel | Object | 否 | - | 搜索列表的请求 model (getListApi) |
|
|
29
|
+
| queryKey | string | 否 | parentId | 列表的请求 model 的入参 key |
|
|
30
|
+
| searchQueryKey | string | 否 | search | 搜索列表的请求 model 的入参 key |
|
|
31
|
+
| labelKey | string | 否 | label | labelKey |
|
|
32
|
+
| valueKey | string | 否 | value | valueKey |
|
|
33
|
+
| phoneKey | string | 否 | phoneNumber | phoneKey 电话 |
|
|
34
|
+
| totalKey | string | 否 | total | totalKey 选中总数人 |
|
|
35
|
+
| imgKey | string | 否 | img | imgKey 头像 |
|
|
36
|
+
| isUserKey | string | 否 | isUser | 区分部门和人员 |
|
|
37
|
+
| hasPagination | boolean | 否 | true | 列表是否分页 |
|
|
38
|
+
| hasSearch | boolean | 否 | true | 是否有搜索 |
|
|
39
|
+
| hasSearchPagination | boolean | 否 | true | 搜索列表是否分页 |
|
|
40
|
+
| searchPlaceholder | string | 否 | "搜索成员" | 搜索框的提示词 |
|
|
41
|
+
| modal | string | 否 | default | default:选中总数 other:选中人数 |
|
|
42
|
+
| iconCustomFunc | function | 否 | - | 图片的回调函数,入参 item,返回地址 |
|
|
43
|
+
| LableSlot | function | 否 | - | label 插槽,入参 item,返回 DOM |
|
|
44
|
+
| ItemRender | function | 否 | - | 整行 item 自定义渲染 |
|
|
45
|
+
| SelectItemRender | function | 否 | - | 选中后 item 自定义渲染 |
|
|
46
|
+
|
|
47
|
+
# 组件开发流程
|
|
48
|
+
|
|
49
|
+
- 在 config/webpack.config.js 中按需修改 library 配置的文件名
|
|
50
|
+
- 在 config/webpack.config.js 中按需修改 alias 配置的包名,便于本地调试
|
|
51
|
+
- 在 tsconfig.json 中按需修改 paths 配置的包名,解决 ts 报错问题
|
|
52
|
+
- npm run dev
|
|
53
|
+
|
|
54
|
+
## 文件目录
|
|
55
|
+
|
|
56
|
+
- example 本地开发测试代码
|
|
57
|
+
- src 组件源码
|
|
58
|
+
|
|
59
|
+
## 命令
|
|
60
|
+
|
|
61
|
+
- Mac 执行该命令,设置 pre-commit 为可执行文件
|
|
62
|
+
|
|
63
|
+
- npm run mac-chmod
|
|
64
|
+
- chmod +x .husky && chmod +x .husky/pre-commit
|
|
65
|
+
|
|
66
|
+
- 生成文档:npm run docs
|
|
67
|
+
- 本地运行:npm run dev
|
|
68
|
+
|
|
69
|
+
## 发布
|
|
70
|
+
|
|
71
|
+
- npm 源和云效源都需要发布
|
|
72
|
+
|
|
73
|
+
- 命令:npm publish --access public
|
|
74
|
+
- 发布目录:
|
|
75
|
+
- src
|
|
76
|
+
|
|
77
|
+
### nrm
|
|
78
|
+
|
|
79
|
+
- 安装
|
|
80
|
+
npm install -g nrm
|
|
81
|
+
- 增加源
|
|
82
|
+
nrm add aliyun https://packages.aliyun.com/62046985b3ead41b374a17f7/npm/npm-registry/
|
|
83
|
+
- 切换源
|
|
84
|
+
nrm use aliyun
|
|
85
|
+
nrm use npm
|
|
86
|
+
- 登录(账号密码在 https://packages.aliyun.com/npm/npm-registry/guide 查看)
|
|
87
|
+
npm login --registry=https://packages.aliyun.com/62046985b3ead41b374a17f7/npm/npm-registry/
|
|
88
|
+
|
|
89
|
+
## 配置
|
|
90
|
+
|
|
91
|
+
### 配置文件
|
|
92
|
+
|
|
93
|
+
- 本地配置文件:config/config.js
|
|
94
|
+
|
|
95
|
+
### webpack 配置文件
|
|
96
|
+
|
|
97
|
+
- config/webpack.config.js
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Checkbox } from "antd";
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import { Checkbox, Avatar } from "antd";
|
|
3
3
|
|
|
4
4
|
import defaultAvatar from "../../assets/imgs/defaultAvatar.png";
|
|
5
5
|
import department from "../../assets/imgs/department.png";
|
|
@@ -12,35 +12,61 @@ function ItemRenter(props, ref) {
|
|
|
12
12
|
valueKey = "value",
|
|
13
13
|
isUserKey = "isUser",
|
|
14
14
|
imgKey = "img",
|
|
15
|
+
phoneKey = "phoneNumber",
|
|
15
16
|
isCheck = false,
|
|
16
17
|
item,
|
|
17
18
|
handleClickItem,
|
|
18
19
|
handleCheckItem,
|
|
19
|
-
noNextLevel = false
|
|
20
|
+
noNextLevel = false,
|
|
21
|
+
iconFunc,
|
|
22
|
+
LableSlot,
|
|
23
|
+
ItemRender,
|
|
20
24
|
} = props;
|
|
21
25
|
|
|
22
|
-
|
|
23
26
|
return (
|
|
24
27
|
<>
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{(item[isUserKey] || noNextLevel) ? null : <div
|
|
32
|
-
className={`next ${isCheck ? "noNext" : ""}`}
|
|
33
|
-
onClick={() => {
|
|
28
|
+
{ItemRender ? (
|
|
29
|
+
<ItemRender
|
|
30
|
+
key={item[valueKey]}
|
|
31
|
+
item={item}
|
|
32
|
+
handleCheckItem={() => handleCheckItem(item)}
|
|
33
|
+
handleClickItem={() => {
|
|
34
34
|
if (isCheck) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
handleClickItem(item);
|
|
38
38
|
}}
|
|
39
|
-
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
></ItemRender>
|
|
40
|
+
) : (
|
|
41
|
+
<div className="group-selection-list-item" key={item[valueKey]}>
|
|
42
|
+
<div className="avatar" onClick={() => handleCheckItem(item)}>
|
|
43
|
+
<Checkbox className="checkbox-item-box" checked={isCheck} />
|
|
44
|
+
<Avatar className="avatar-item" size={32} src={iconFunc(item)} />
|
|
45
|
+
{LableSlot ? (
|
|
46
|
+
<LableSlot item={item}></LableSlot>
|
|
47
|
+
) : (
|
|
48
|
+
<div className="label">
|
|
49
|
+
<div>{item[labelKey]}</div>
|
|
50
|
+
<div className="label-phone">{item[phoneKey]}</div>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
{item[isUserKey] || noNextLevel ? null : (
|
|
55
|
+
<div
|
|
56
|
+
className={`next ${isCheck ? "noNext" : ""}`}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
if (isCheck) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
handleClickItem(item);
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
下级
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
</>
|
|
44
70
|
);
|
|
45
71
|
}
|
|
46
72
|
|
|
@@ -1,33 +1,57 @@
|
|
|
1
|
-
.group-selection-list-item {
|
|
2
|
-
display: flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
justify-content: space-between;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.avatar {
|
|
8
|
-
display: flex;
|
|
9
|
-
align-items: center;
|
|
10
|
-
cursor: pointer;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
.group-selection-list-item {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
padding: 8px 6px;
|
|
6
|
+
margin-bottom: 16px;
|
|
7
|
+
.avatar {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
|
|
12
|
+
.label {
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
color: #333333;
|
|
15
|
+
.label-phone {
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
color: #999999;
|
|
18
|
+
line-height: 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.avatar-item {
|
|
23
|
+
margin: 0 6px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.next {
|
|
28
|
+
color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.noNext {
|
|
34
|
+
opacity: 0.5;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.checkbox-item-box {
|
|
38
|
+
.ant-checkbox-inner {
|
|
39
|
+
border-radius: 4px;
|
|
40
|
+
}
|
|
41
|
+
.ant-checkbox-checked:after {
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
}
|
|
44
|
+
// 选中状态:深蓝色底色
|
|
45
|
+
.ant-checkbox-checked {
|
|
46
|
+
.ant-checkbox-inner {
|
|
47
|
+
background-color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
48
|
+
border-color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
background: #f7f7f7;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState, forwardRef } from "react";
|
|
2
2
|
import { Input, message, Pagination } from "antd";
|
|
3
3
|
|
|
4
|
+
import SearchIcon from "../../assets/imgs/searchIcon.png";
|
|
4
5
|
import "./index.less";
|
|
5
6
|
|
|
6
7
|
function SearchRenter(props, ref) {
|
|
@@ -79,7 +80,17 @@ function SearchRenter(props, ref) {
|
|
|
79
80
|
|
|
80
81
|
return (
|
|
81
82
|
<>
|
|
82
|
-
{hasSearch ?
|
|
83
|
+
{hasSearch ? (
|
|
84
|
+
<Input.Search
|
|
85
|
+
className="group-selection-search-input"
|
|
86
|
+
prefix={<img className="group-selection-search-icon" src={SearchIcon}></img>}
|
|
87
|
+
onSearch={onSearch}
|
|
88
|
+
enterButton
|
|
89
|
+
placeholder={searchPlaceholder}
|
|
90
|
+
allowClear
|
|
91
|
+
/>
|
|
92
|
+
) : null}
|
|
93
|
+
|
|
83
94
|
{search ? (
|
|
84
95
|
<div className="group-selection-search-view">
|
|
85
96
|
<div className="group-selection-search-list">
|
|
@@ -1,36 +1,51 @@
|
|
|
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:
|
|
8
|
-
width: calc(100% -
|
|
9
|
-
height: calc(100% -
|
|
10
|
-
overflow-y: hidden;
|
|
11
|
-
z-index: 10;
|
|
12
|
-
background-color: #fff;
|
|
13
|
-
|
|
14
|
-
.group-selection-search-list {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
overflow-y: auto;
|
|
18
|
-
padding:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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: 14px;
|
|
8
|
+
width: calc(100% - 28px);
|
|
9
|
+
height: calc(100% - 60px);
|
|
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: 14px;
|
|
19
|
+
margin-bottom: 40px;
|
|
20
|
+
|
|
21
|
+
// &::-webkit-scrollbar {
|
|
22
|
+
// display: none;
|
|
23
|
+
// }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.group-selection-search-pagination {
|
|
27
|
+
position: absolute;
|
|
28
|
+
left: 0;
|
|
29
|
+
bottom: 0;
|
|
30
|
+
width: 100%;
|
|
31
|
+
height: 40px;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
background-color: #fff;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
.group-selection-search-icon {
|
|
39
|
+
width: 16px;
|
|
40
|
+
height: auto;
|
|
41
|
+
margin-right: 6px;
|
|
42
|
+
}
|
|
43
|
+
.group-selection-search-input {
|
|
44
|
+
.ant-input-affix-wrapper {
|
|
45
|
+
border-radius: 8px !important;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ant-input-group-addon {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.less
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
position: relative;
|
|
27
27
|
height: 100%;
|
|
28
28
|
overflow: hidden;
|
|
29
|
-
padding:
|
|
29
|
+
padding: 14px;
|
|
30
30
|
border-right: 1px solid #e8e8e8;
|
|
31
31
|
|
|
32
32
|
// .group-selection-search-view {
|
|
@@ -75,15 +75,33 @@
|
|
|
75
75
|
|
|
76
76
|
.group-selection-list-check {
|
|
77
77
|
margin: 12px 0;
|
|
78
|
+
|
|
79
|
+
.group-selection-all-checkbox-box {
|
|
80
|
+
margin-right: 6px;
|
|
81
|
+
|
|
82
|
+
.ant-checkbox-inner {
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
}
|
|
85
|
+
.ant-checkbox-checked:after {
|
|
86
|
+
border-radius: 4px;
|
|
87
|
+
}
|
|
88
|
+
// 选中状态:深蓝色底色
|
|
89
|
+
.ant-checkbox-checked {
|
|
90
|
+
.ant-checkbox-inner {
|
|
91
|
+
background-color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
92
|
+
border-color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
.group-selection-list-view {
|
|
81
99
|
flex: 1;
|
|
82
100
|
overflow: auto;
|
|
83
101
|
|
|
84
|
-
&::-webkit-scrollbar {
|
|
85
|
-
|
|
86
|
-
}
|
|
102
|
+
// &::-webkit-scrollbar {
|
|
103
|
+
// display: none;
|
|
104
|
+
// }
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
// .group-selection-list-item {
|
|
@@ -124,43 +142,81 @@
|
|
|
124
142
|
.group-selection-detail {
|
|
125
143
|
position: relative;
|
|
126
144
|
height: 100%;
|
|
127
|
-
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
padding: 14px;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
|
|
150
|
+
// &::-webkit-scrollbar {
|
|
151
|
+
// display: none;
|
|
152
|
+
// }
|
|
128
153
|
|
|
129
|
-
|
|
130
|
-
|
|
154
|
+
.group-selection-detail-empty {
|
|
155
|
+
height: 100%;
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
.ant-empty-image {
|
|
161
|
+
margin-bottom: 24px;
|
|
162
|
+
}
|
|
163
|
+
.ant-empty-description {
|
|
164
|
+
font-size: 14px;
|
|
165
|
+
color: #777777;
|
|
166
|
+
}
|
|
131
167
|
}
|
|
168
|
+
|
|
132
169
|
.group-selection-detail-title {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
170
|
+
height: 36px;
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: space-between;
|
|
174
|
+
|
|
175
|
+
.group-selection-detail-close {
|
|
176
|
+
color: var(--ant-primary-color, rgb(64, 62, 245));
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.group-selection-detail-total {
|
|
181
|
+
font-weight: bold;
|
|
182
|
+
font-size: 14px;
|
|
183
|
+
color: #333333;
|
|
184
|
+
}
|
|
136
185
|
}
|
|
137
186
|
|
|
138
187
|
.group-selection-detail-list {
|
|
188
|
+
flex: 1;
|
|
139
189
|
display: flex;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
padding: 14px 0 24px 0;
|
|
192
|
+
overflow: auto;
|
|
143
193
|
|
|
144
194
|
.group-selection-detail-item {
|
|
145
195
|
flex-shrink: 0;
|
|
146
196
|
display: flex;
|
|
147
197
|
align-items: center;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
198
|
+
justify-content: space-between;
|
|
199
|
+
padding: 8px 6px;
|
|
200
|
+
margin-bottom: 14px;
|
|
151
201
|
border-radius: 8px;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
202
|
+
.avatar-text-box {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
|
|
206
|
+
.label {
|
|
207
|
+
font-size: 16px;
|
|
208
|
+
color: #333333;
|
|
209
|
+
margin-right: 12px;
|
|
210
|
+
.label-phone {
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
color: #999999;
|
|
213
|
+
line-height: 1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.avatar-item {
|
|
218
|
+
margin-right: 7px;
|
|
219
|
+
}
|
|
164
220
|
}
|
|
165
221
|
}
|
|
166
222
|
}
|
package/src/index.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { useEffect, useState, useImperativeHandle, forwardRef, useReducer, useRef } from "react";
|
|
2
|
-
import { Input, Breadcrumb, Checkbox, message, Spin, Pagination, ConfigProvider } from "antd";
|
|
1
|
+
import { useEffect, useState, useImperativeHandle, forwardRef, useReducer, useRef, useMemo } from "react";
|
|
2
|
+
import { Input, Breadcrumb, Checkbox, message, Spin, Pagination, ConfigProvider, Empty, Avatar } from "antd";
|
|
3
3
|
import zhCN from "antd/es/locale/zh_CN";
|
|
4
|
-
import {
|
|
4
|
+
import { CloseOutlined } from "@ant-design/icons";
|
|
5
5
|
import "moment/locale/zh-cn";
|
|
6
6
|
import _ from "lodash";
|
|
7
7
|
|
|
8
8
|
import defaultAvatar from "./assets/imgs/defaultAvatar.png";
|
|
9
9
|
import department from "./assets/imgs/department.png";
|
|
10
|
+
import EmptyIcon from "./assets/imgs/emptyIcon.png";
|
|
10
11
|
import SearchRenter from "./components/SearchRenter";
|
|
11
12
|
import ItemRenter from "./components/ItemRenter";
|
|
12
13
|
|
|
@@ -20,10 +21,27 @@ function GroupSelection(props, ref) {
|
|
|
20
21
|
labelKey = "label",
|
|
21
22
|
valueKey = "value",
|
|
22
23
|
isUserKey = "isUser",
|
|
24
|
+
/** 手机号字段 */
|
|
25
|
+
phoneKey = "phoneNumber",
|
|
26
|
+
/** 选中总数人字段 */
|
|
27
|
+
totalKey = "total",
|
|
28
|
+
/** default:选中总数,other:根据 totalKey计算人数 */
|
|
29
|
+
modal = "default",
|
|
23
30
|
imgKey = "img",
|
|
24
31
|
hasPagination = true,
|
|
25
32
|
modelGetType = "getList",
|
|
26
33
|
allowClear = false,
|
|
34
|
+
unit = "人",
|
|
35
|
+
/** icon自定义 */
|
|
36
|
+
iconCustomFunc,
|
|
37
|
+
/** label插槽 */
|
|
38
|
+
LableSlot,
|
|
39
|
+
/** 列表item自定义渲染 */
|
|
40
|
+
ItemRender,
|
|
41
|
+
/** 选中item自定义渲染 */
|
|
42
|
+
SelectItemRender,
|
|
43
|
+
/** 选中文案自定义 */
|
|
44
|
+
SelectTitleRender,
|
|
27
45
|
} = props;
|
|
28
46
|
const [breadcrumb, setBreadcrumb] = useState([
|
|
29
47
|
{
|
|
@@ -79,6 +97,12 @@ function GroupSelection(props, ref) {
|
|
|
79
97
|
});
|
|
80
98
|
};
|
|
81
99
|
|
|
100
|
+
const totalSum = useMemo(() => {
|
|
101
|
+
return selectedList?.reduce((pre, cur) => {
|
|
102
|
+
return pre + (cur?.[totalKey] || 0);
|
|
103
|
+
}, 0);
|
|
104
|
+
}, [selectedList]);
|
|
105
|
+
|
|
82
106
|
useEffect(() => {
|
|
83
107
|
getTreeData();
|
|
84
108
|
}, []);
|
|
@@ -149,17 +173,29 @@ function GroupSelection(props, ref) {
|
|
|
149
173
|
return is;
|
|
150
174
|
};
|
|
151
175
|
|
|
176
|
+
const iconFunc = (item) => {
|
|
177
|
+
// 优先执行自定义函数,拿到结果
|
|
178
|
+
const customResult = iconCustomFunc?.(item);
|
|
179
|
+
// 自定义有返回值直接用,否则走默认逻辑
|
|
180
|
+
if (customResult) return customResult;
|
|
181
|
+
// 默认图片逻辑
|
|
182
|
+
return item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department;
|
|
183
|
+
};
|
|
184
|
+
|
|
152
185
|
const Renter = (item, noNextLevel) => {
|
|
153
186
|
const isCheck = handleCheck(item);
|
|
154
187
|
return (
|
|
155
188
|
<ItemRenter
|
|
156
189
|
{...props}
|
|
157
190
|
item={item}
|
|
191
|
+
iconFunc={iconFunc}
|
|
158
192
|
isCheck={isCheck}
|
|
159
193
|
handleCheckItem={handleCheckItem}
|
|
160
194
|
handleCheckDelete={handleCheckDelete}
|
|
161
195
|
handleClickItem={handleClickItem}
|
|
162
196
|
noNextLevel={noNextLevel}
|
|
197
|
+
LableSlot={LableSlot}
|
|
198
|
+
ItemRender={ItemRender}
|
|
163
199
|
/>
|
|
164
200
|
);
|
|
165
201
|
};
|
|
@@ -188,7 +224,12 @@ function GroupSelection(props, ref) {
|
|
|
188
224
|
})}
|
|
189
225
|
</Breadcrumb>
|
|
190
226
|
<div className="group-selection-list-check">
|
|
191
|
-
<Checkbox
|
|
227
|
+
<Checkbox
|
|
228
|
+
className="group-selection-all-checkbox-box"
|
|
229
|
+
onChange={handleCheckAll}
|
|
230
|
+
checked={checkAll && !!selectList.length}
|
|
231
|
+
/>
|
|
232
|
+
全选
|
|
192
233
|
</div>
|
|
193
234
|
<div className="group-selection-list-view">
|
|
194
235
|
{selectList.map((item, index) => {
|
|
@@ -209,18 +250,58 @@ function GroupSelection(props, ref) {
|
|
|
209
250
|
</div>
|
|
210
251
|
<div className="group-selection-detail">
|
|
211
252
|
{allowClear ? (
|
|
212
|
-
<div className="group-selection-detail-title"
|
|
213
|
-
|
|
253
|
+
<div className="group-selection-detail-title">
|
|
254
|
+
<div className="group-selection-detail-total">
|
|
255
|
+
{SelectTitleRender ? (
|
|
256
|
+
<SelectTitleRender value={modal === "default" ? selectedList?.length : totalSum}></SelectTitleRender>
|
|
257
|
+
) : (
|
|
258
|
+
` 已选:${modal === "default" ? selectedList?.length : totalSum}
|
|
259
|
+
${unit}`
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
<div className="group-selection-detail-close" onClick={handleAllowClear}>
|
|
263
|
+
清空
|
|
264
|
+
</div>
|
|
214
265
|
</div>
|
|
215
266
|
) : null}
|
|
267
|
+
{!selectedList?.length && (
|
|
268
|
+
<div className="group-selection-detail-empty">
|
|
269
|
+
<Empty
|
|
270
|
+
description={"请选择部门或人员"}
|
|
271
|
+
image={EmptyIcon}
|
|
272
|
+
imageStyle={{
|
|
273
|
+
width: "250px",
|
|
274
|
+
height: "99px",
|
|
275
|
+
}}
|
|
276
|
+
></Empty>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
216
279
|
|
|
217
280
|
<div className="group-selection-detail-list">
|
|
218
281
|
{selectedList.map((item, index) => {
|
|
219
|
-
return (
|
|
282
|
+
return SelectItemRender ? (
|
|
283
|
+
<SelectItemRender
|
|
284
|
+
key={item[valueKey]}
|
|
285
|
+
item={item}
|
|
286
|
+
handleCheckDelete={() => handleCheckDelete(item)}
|
|
287
|
+
></SelectItemRender>
|
|
288
|
+
) : (
|
|
220
289
|
<div className="group-selection-detail-item" key={item[valueKey]}>
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
290
|
+
<div className="avatar-text-box">
|
|
291
|
+
<Avatar className="avatar-item" size={32} src={iconFunc(item)} />
|
|
292
|
+
{LableSlot ? (
|
|
293
|
+
<LableSlot item={item}></LableSlot>
|
|
294
|
+
) : (
|
|
295
|
+
<div className="label">
|
|
296
|
+
<div>{item[labelKey]}</div>
|
|
297
|
+
<div className="label-phone">{item[phoneKey]}</div>
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
<CloseOutlined
|
|
302
|
+
style={{ cursor: "pointer", color: "#6a6a6a" }}
|
|
303
|
+
onClick={() => handleCheckDelete(item)}
|
|
304
|
+
/>
|
|
224
305
|
</div>
|
|
225
306
|
);
|
|
226
307
|
})}
|