@hzab/group-user-selector 0.0.9 → 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 CHANGED
@@ -1,3 +1,15 @@
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
+
9
+ # @hzab/group-user-selector@0.0.10
10
+
11
+ feat: 搜索情况下默认不展示下级
12
+
1
13
  # @hzab/group-user-selector@0.0.9
2
14
 
3
15
  feat: 增加一键清除 API
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 | 否 | - | 列表的请求 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
- | imgKey | string | 否 | img | imgKey 头像 |
34
- | isUserKey | string | 否 | isUser | 区分部门和人员 |
35
- | hasPagination | boolean | 否 | true | 列表是否分页 |
36
- | hasSearch | boolean | 否 | true | 是否有搜索 |
37
- | hasSearchPagination | boolean | 否 | true | 搜索列表是否分页 |
38
- | searchPlaceholder | string | 否 | "搜索成员"| 搜索框的提示词 |
39
-
40
- # 组件开发流程
41
-
42
- - config/webpack.config.js 中按需修改 library 配置的文件名
43
- - config/webpack.config.js 中按需修改 alias 配置的包名,便于本地调试
44
- - tsconfig.json 中按需修改 paths 配置的包名,解决 ts 报错问题
45
- - npm run dev
46
-
47
- ## 文件目录
48
-
49
- - example 本地开发测试代码
50
- - src 组件源码
51
-
52
- ## 命令
53
-
54
- - Mac 执行该命令,设置 pre-commit 为可执行文件
55
-
56
- - npm run mac-chmod
57
- - chmod +x .husky && chmod +x .husky/pre-commit
58
-
59
- - 生成文档:npm run docs
60
- - 本地运行:npm run dev
61
-
62
- ## 发布
63
-
64
- - npm 源和云效源都需要发布
65
-
66
- - 命令:npm publish --access public
67
- - 发布目录:
68
- - src
69
-
70
- ### nrm
71
-
72
- - 安装
73
- npm install -g nrm
74
- - 增加源
75
- nrm add aliyun https://packages.aliyun.com/62046985b3ead41b374a17f7/npm/npm-registry/
76
- - 切换源
77
- nrm use aliyun
78
- nrm use npm
79
- - 登录(账号密码在 https://packages.aliyun.com/npm/npm-registry/guide 查看)
80
- npm login --registry=https://packages.aliyun.com/62046985b3ead41b374a17f7/npm/npm-registry/
81
-
82
- ## 配置
83
-
84
- ### 配置文件
85
-
86
- - 本地配置文件:config/config.js
87
-
88
- ### webpack 配置文件
89
-
90
- - config/webpack.config.js
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
@@ -1,52 +1,57 @@
1
- {
2
- "name": "@hzab/group-user-selector",
3
- "version": "0.0.9",
4
- "description": "",
5
- "main": "src",
6
- "scripts": {
7
- "dev": "webpack serve -c ./config/webpack.config.js --env local",
8
- "build": "webpack -c ./config/webpack.config.js --env production",
9
- "publish-patch": "npm version patch && npm publish --access public",
10
- "publish-minor": "npm version minor && npm publish --access public",
11
- "publish-major": "npm version major && npm publish --access public",
12
- "prepare": "husky install",
13
- "mac-chmod": "chmod +x .husky && chmod +x .husky/pre-commit",
14
- "docs": "typedoc"
15
- },
16
- "files": [
17
- "src",
18
- "CHANGELOG.md"
19
- ],
20
- "keywords": [],
21
- "author": "CaiYansong",
22
- "license": "ISC",
23
- "devDependencies": {
24
- "@hzab/permissions": "^1.0.0",
25
- "@hzab/webpack-config": "^1.0.0",
26
- "@types/react": "^17.0.62",
27
- "@types/react-dom": "^17.0.20",
28
- "antd": "^4.14.0",
29
- "axios": "^1.4.0",
30
- "eslint": "^8.30.0",
31
- "less": "^4.1.3",
32
- "mobx": "^6.7.0",
33
- "mobx-react": "^7.6.0",
34
- "react": "^17.0.2",
35
- "react-dom": "^17.0.2",
36
- "react-router-dom": "^6.14.1",
37
- "typedoc": "^0.24.8",
38
- "typescript": "^4.9.4"
39
- },
40
- "directories": {
41
- "src": "src"
42
- },
43
- "lint-staged": {
44
- "**.{js,jsx,ts,tsx,css,scss,less,json,html}": [
45
- "prettier --write"
46
- ]
47
- },
48
- "dependencies": {
49
- "@hzab/data-model": "^1.7.0",
50
- "lodash": "^4.17.21"
51
- }
52
- }
1
+ {
2
+ "name": "@hzab/group-user-selector",
3
+ "version": "0.0.11-alpha.0",
4
+ "description": "",
5
+ "main": "src",
6
+ "scripts": {
7
+ "dev": "webpack serve -c ./config/webpack.config.js --env local",
8
+ "build": "webpack -c ./config/webpack.config.js --env production",
9
+ "version:alpha": "npm version prerelease --preid=alpha",
10
+ "publish:alpha": "npm publish --tag alpha",
11
+ "version:beta": "npm version prerelease --preid=beta",
12
+ "publish:beta": "npm publish --tag beta",
13
+ "version:patch": "npm version patch",
14
+ "version:minior": "npm version minor",
15
+ "version:major": "npm version major",
16
+ "publish:stable": "npm publish --access public",
17
+ "prepare": "husky install",
18
+ "mac-chmod": "chmod +x .husky && chmod +x .husky/pre-commit",
19
+ "docs": "typedoc"
20
+ },
21
+ "files": [
22
+ "src",
23
+ "CHANGELOG.md"
24
+ ],
25
+ "keywords": [],
26
+ "author": "CaiYansong",
27
+ "license": "ISC",
28
+ "devDependencies": {
29
+ "@hzab/permissions": "^1.0.0",
30
+ "@hzab/webpack-config": "^1.0.0",
31
+ "@types/react": "^17.0.62",
32
+ "@types/react-dom": "^17.0.20",
33
+ "antd": "^4.14.0",
34
+ "axios": "^1.4.0",
35
+ "eslint": "^8.30.0",
36
+ "less": "^4.1.3",
37
+ "mobx": "^6.7.0",
38
+ "mobx-react": "^7.6.0",
39
+ "react": "^17.0.2",
40
+ "react-dom": "^17.0.2",
41
+ "react-router-dom": "^6.14.1",
42
+ "typedoc": "^0.24.8",
43
+ "typescript": "^4.9.4"
44
+ },
45
+ "directories": {
46
+ "src": "src"
47
+ },
48
+ "lint-staged": {
49
+ "**.{js,jsx,ts,tsx,css,scss,less,json,html}": [
50
+ "prettier --write"
51
+ ]
52
+ },
53
+ "dependencies": {
54
+ "@hzab/data-model": "^1.7.0",
55
+ "lodash": "^4.17.21"
56
+ }
57
+ }
Binary file
Binary file
Binary file
@@ -1,46 +1,73 @@
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);
1
+ import { forwardRef } from "react";
2
+ import { Checkbox, Avatar } 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
+ phoneKey = "phoneNumber",
16
+ isCheck = false,
17
+ item,
18
+ handleClickItem,
19
+ handleCheckItem,
20
+ noNextLevel = false,
21
+ iconFunc,
22
+ LableSlot,
23
+ ItemRender,
24
+ } = props;
25
+
26
+ return (
27
+ <>
28
+ {ItemRender ? (
29
+ <ItemRender
30
+ key={item[valueKey]}
31
+ item={item}
32
+ handleCheckItem={() => handleCheckItem(item)}
33
+ handleClickItem={() => {
34
+ if (isCheck) {
35
+ return;
36
+ }
37
+ handleClickItem(item);
38
+ }}
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
+ </>
70
+ );
71
+ }
72
+
73
+ export default forwardRef(ItemRenter);
@@ -1,33 +1,57 @@
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
- }
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,108 +1,119 @@
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
- searchPlaceholder = "搜索成员",
11
- hasSearchPagination = true,
12
- hasSearch = true,
13
- ItemRenter,
14
- setLoading = () => {},
15
- } = props;
16
-
17
- const [search, setSearch] = useState("");
18
- const [searchList, setSearchList] = useState([]);
19
- const [total, setTotal] = useState(0);
20
- const [pageData, setPageData] = useState(
21
- hasSearchPagination
22
- ? {
23
- pageSize: 10,
24
- pageNum: 1,
25
- }
26
- : {},
27
- );
28
-
29
- const handleSearch = (data) => {
30
- setLoading && setLoading(true);
31
- searchModel &&
32
- searchModel
33
- .getList({ ...data })
34
- .then((res) => {
35
- setSearchList(res?.list || res || []);
36
- setTotal(res?.pagination?.total || res?.total || 0);
37
- })
38
- .catch((err) => {
39
- message.error(err._message || "未知错误");
40
- })
41
- .finally(() => {
42
- setLoading && setLoading(false);
43
- });
44
- };
45
-
46
- const handleChangePage = (pageNum, pageSize) => {
47
- const pages = {
48
- pageNum: pageSize !== pageData?.pageSize ? 1 : pageNum,
49
- pageSize,
50
- };
51
- setPageData({
52
- ...pages,
53
- });
54
- handleSearch({
55
- [searchQueryKey]: search,
56
- ...pages,
57
- });
58
- };
59
-
60
- const onSearch = (value) => {
61
- setSearch(value);
62
- if (!value) {
63
- setPageData({
64
- pageSize: 10,
65
- pageNum: 1,
66
- });
67
- return;
68
- }
69
- const data = {
70
- pageSize: pageData?.pageSize || 10,
71
- pageNum: 1,
72
- };
73
- setPageData(data);
74
- handleSearch({
75
- [searchQueryKey]: value,
76
- ...data,
77
- });
78
- };
79
-
80
- return (
81
- <>
82
- {hasSearch ? <Input.Search onSearch={onSearch} enterButton placeholder={searchPlaceholder} allowClear /> : null}
83
- {search ? (
84
- <div className="group-selection-search-view">
85
- <div className="group-selection-search-list">
86
- {searchList.map((item, index) => {
87
- return ItemRenter(item);
88
- })}
89
- </div>
90
-
91
- {hasSearchPagination ? (
92
- <div className="group-selection-search-pagination">
93
- <Pagination
94
- current={pageData?.pageNum}
95
- total={total}
96
- pageSize={pageData?.pageSize}
97
- onChange={handleChangePage}
98
- size="small"
99
- />
100
- </div>
101
- ) : null}
102
- </div>
103
- ) : null}
104
- </>
105
- );
106
- }
107
-
108
- export default forwardRef(SearchRenter);
1
+ import { useEffect, useState, forwardRef } from "react";
2
+ import { Input, message, Pagination } from "antd";
3
+
4
+ import SearchIcon from "../../assets/imgs/searchIcon.png";
5
+ import "./index.less";
6
+
7
+ function SearchRenter(props, ref) {
8
+ const {
9
+ searchModel,
10
+ searchQueryKey = "search",
11
+ searchPlaceholder = "搜索成员",
12
+ hasSearchPagination = true,
13
+ hasSearch = true,
14
+ ItemRenter,
15
+ setLoading = () => {},
16
+ } = props;
17
+
18
+ const [search, setSearch] = useState("");
19
+ const [searchList, setSearchList] = useState([]);
20
+ const [total, setTotal] = useState(0);
21
+ const [pageData, setPageData] = useState(
22
+ hasSearchPagination
23
+ ? {
24
+ pageSize: 10,
25
+ pageNum: 1,
26
+ }
27
+ : {},
28
+ );
29
+
30
+ const handleSearch = (data) => {
31
+ setLoading && setLoading(true);
32
+ searchModel &&
33
+ searchModel
34
+ .getList({ ...data })
35
+ .then((res) => {
36
+ setSearchList(res?.list || res || []);
37
+ setTotal(res?.pagination?.total || res?.total || 0);
38
+ })
39
+ .catch((err) => {
40
+ message.error(err._message || "未知错误");
41
+ })
42
+ .finally(() => {
43
+ setLoading && setLoading(false);
44
+ });
45
+ };
46
+
47
+ const handleChangePage = (pageNum, pageSize) => {
48
+ const pages = {
49
+ pageNum: pageSize !== pageData?.pageSize ? 1 : pageNum,
50
+ pageSize,
51
+ };
52
+ setPageData({
53
+ ...pages,
54
+ });
55
+ handleSearch({
56
+ [searchQueryKey]: search,
57
+ ...pages,
58
+ });
59
+ };
60
+
61
+ const onSearch = (value) => {
62
+ setSearch(value);
63
+ if (!value) {
64
+ setPageData({
65
+ pageSize: 10,
66
+ pageNum: 1,
67
+ });
68
+ return;
69
+ }
70
+ const data = {
71
+ pageSize: pageData?.pageSize || 10,
72
+ pageNum: 1,
73
+ };
74
+ setPageData(data);
75
+ handleSearch({
76
+ [searchQueryKey]: value,
77
+ ...data,
78
+ });
79
+ };
80
+
81
+ return (
82
+ <>
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
+
94
+ {search ? (
95
+ <div className="group-selection-search-view">
96
+ <div className="group-selection-search-list">
97
+ {searchList.map((item, index) => {
98
+ return ItemRenter(item, true);
99
+ })}
100
+ </div>
101
+
102
+ {hasSearchPagination ? (
103
+ <div className="group-selection-search-pagination">
104
+ <Pagination
105
+ current={pageData?.pageNum}
106
+ total={total}
107
+ pageSize={pageData?.pageSize}
108
+ onChange={handleChangePage}
109
+ size="small"
110
+ />
111
+ </div>
112
+ ) : null}
113
+ </div>
114
+ ) : null}
115
+ </>
116
+ );
117
+ }
118
+
119
+ export default forwardRef(SearchRenter);
@@ -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: 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
- }
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: 24px;
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
- display: none;
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
- overflow: auto;
145
+ display: flex;
146
+ flex-direction: column;
147
+ padding: 14px;
148
+ overflow: hidden;
149
+
150
+ // &::-webkit-scrollbar {
151
+ // display: none;
152
+ // }
128
153
 
129
- &::-webkit-scrollbar {
130
- display: none;
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
- text-align: end;
134
- color: var(--ant-primary-color);
135
- cursor: pointer;
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
- align-items: center;
141
- flex-wrap: wrap;
142
- padding: 24px;
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
- padding: 4px 8px;
149
- margin: 0 12px 12px 0;
150
- color: #e8e8e8;
198
+ justify-content: space-between;
199
+ padding: 8px 6px;
200
+ margin-bottom: 14px;
151
201
  border-radius: 8px;
152
- background-color: aliceblue;
153
-
154
- img {
155
- width: 32px;
156
- height: auto;
157
- margin-right: 12px;
158
- }
159
-
160
- .label {
161
- font-size: 16px;
162
- color: #141414;
163
- margin-right: 12px;
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 { CloseCircleFilled, SearchOutlined } from "@ant-design/icons";
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,16 +173,29 @@ function GroupSelection(props, ref) {
149
173
  return is;
150
174
  };
151
175
 
152
- const Renter = (item) => {
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
+
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}
196
+ noNextLevel={noNextLevel}
197
+ LableSlot={LableSlot}
198
+ ItemRender={ItemRender}
162
199
  />
163
200
  );
164
201
  };
@@ -187,11 +224,16 @@ function GroupSelection(props, ref) {
187
224
  })}
188
225
  </Breadcrumb>
189
226
  <div className="group-selection-list-check">
190
- <Checkbox onChange={handleCheckAll} checked={checkAll && !!selectList.length} /> 全选
227
+ <Checkbox
228
+ className="group-selection-all-checkbox-box"
229
+ onChange={handleCheckAll}
230
+ checked={checkAll && !!selectList.length}
231
+ />
232
+ 全选
191
233
  </div>
192
234
  <div className="group-selection-list-view">
193
235
  {selectList.map((item, index) => {
194
- return Renter(item);
236
+ return Renter(item, false);
195
237
  })}
196
238
  </div>
197
239
  {hasPagination ? (
@@ -208,18 +250,58 @@ function GroupSelection(props, ref) {
208
250
  </div>
209
251
  <div className="group-selection-detail">
210
252
  {allowClear ? (
211
- <div className="group-selection-detail-title" onClick={handleAllowClear}>
212
- 清空
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>
213
265
  </div>
214
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
+ )}
215
279
 
216
280
  <div className="group-selection-detail-list">
217
281
  {selectedList.map((item, index) => {
218
- return (
282
+ return SelectItemRender ? (
283
+ <SelectItemRender
284
+ key={item[valueKey]}
285
+ item={item}
286
+ handleCheckDelete={() => handleCheckDelete(item)}
287
+ ></SelectItemRender>
288
+ ) : (
219
289
  <div className="group-selection-detail-item" key={item[valueKey]}>
220
- <img src={item[imgKey] ? item[imgKey] : item[isUserKey] ? defaultAvatar : department} />
221
- <div className="label">{item[labelKey]}</div>
222
- <CloseCircleFilled style={{ cursor: "pointer" }} onClick={() => handleCheckDelete(item)} />
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
+ />
223
305
  </div>
224
306
  );
225
307
  })}