@tddc/assign-modal 3.0.9 → 3.1.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.
@@ -0,0 +1,315 @@
1
+ import { Input, Empty, Icon, Tooltip, Ellipsis } from 'tntd';
2
+ import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
3
+
4
+ import { debounce } from 'lodash';
5
+ import './index.less';
6
+ import { findSameCodePath, preorder } from './utils';
7
+ import { getText } from '../../locale';
8
+
9
+ let path = []; // 上级机构到当前机构的路径
10
+
11
+ const AssignModal = (props) => {
12
+ const {
13
+ orgList = [],
14
+ dataItem = {},
15
+ appList,
16
+ orgTitle,
17
+ appTitle,
18
+ userTitle,
19
+ userList = [],
20
+ showUser,
21
+ locale,
22
+ } = props;
23
+
24
+ // 封装 getText,自动传入 locale
25
+ const t = (key, ...params) => getText(key, props?.lang, locale, ...params);
26
+ let { appCodes = [], orgCodes = [], orgCode, appCode, accounts, account } = dataItem;
27
+
28
+ const orgMapRef = useRef({});
29
+ const appMapRef = useRef({});
30
+ const userMapRef = useRef({});
31
+
32
+ const rootNode = orgList[0];
33
+
34
+ const [allOrg, allApp, allUser] = useMemo(() => {
35
+ let org = preorder(
36
+ rootNode,
37
+ (key, obj) => {
38
+ orgMapRef.current[key] = obj;
39
+ },
40
+ true,
41
+ );
42
+ let app = appList.map((item) => {
43
+ appMapRef.current[item.value] = item;
44
+ return item.value;
45
+ });
46
+ let user =
47
+ userList?.map((item) => {
48
+ userMapRef.current[item.account] = item;
49
+ return item.account;
50
+ }) || [];
51
+
52
+ return [org, app, user];
53
+ }, [rootNode, appList, userList]);
54
+
55
+ const [checkedKeys, setCheckedKeys] = useState([]);
56
+ const [appKeys, setAppKeys] = useState(appCodes || []);
57
+ const [userKeys, setUserKeys] = useState(accounts || []);
58
+
59
+ const [filterOrg, setFilterOrg] = useState();
60
+ const [filterUser, setFilterUser] = useState();
61
+ const [filterApp, setFilterApp] = useState();
62
+
63
+ useEffect(() => {
64
+ // path 和 allOrgList 赋值
65
+ path = findSameCodePath(rootNode, orgCode);
66
+
67
+ let initOrgs = [];
68
+ let initApps = [];
69
+ let initAccounts = [];
70
+ if (orgCodes.includes('all')) {
71
+ initOrgs = allOrg;
72
+ } else {
73
+ initOrgs = Array.from(new Set([...(orgCodes || []), ...path]));
74
+ }
75
+ if (appCodes.includes('all')) {
76
+ initApps = allApp;
77
+ } else {
78
+ initApps = Array.from(new Set([...(appCodes || []), appCode]));
79
+ }
80
+
81
+ if (showUser) {
82
+ if (accounts.includes('all')) {
83
+ initAccounts = allUser;
84
+ } else {
85
+ initAccounts = Array.from(new Set([...(accounts || []), account]));
86
+ }
87
+ }
88
+ setCheckedKeys(initOrgs);
89
+ setAppKeys(initApps || []);
90
+ setUserKeys(initAccounts || []);
91
+ }, [dataItem]);
92
+
93
+ const debouncedOrgSearch = useCallback(
94
+ debounce((nextValue) => {
95
+ setFilterOrg(nextValue);
96
+ }, 200),
97
+ [],
98
+ );
99
+ const debouncedUserSearch = useCallback(
100
+ debounce((nextValue) => {
101
+ setFilterUser(nextValue);
102
+ }, 200),
103
+ [],
104
+ );
105
+ const debouncedAppSearch = useCallback(
106
+ debounce((nextValue) => {
107
+ setFilterApp(nextValue);
108
+ }, 200),
109
+ [],
110
+ );
111
+ // 获取机构路径的中文显示名并拼接
112
+ const getOrgPathDisplayName = (path = []) => {
113
+ let displayName = '';
114
+ if (path.length > 0) {
115
+ displayName = path.map((item) => orgMapRef.current[item].name).join(' / ');
116
+ }
117
+
118
+ return displayName;
119
+ };
120
+
121
+ // 渲染Org列表
122
+ let orgListDomRender = useMemo(() => {
123
+ const renderOrgItem = (item, lang) => {
124
+ console.log(item, 'item');
125
+
126
+ const mapResult = {
127
+ 1: {
128
+ cn: '职能部门',
129
+ en: 'Func. Dept.',
130
+ icon: 'crowd',
131
+ },
132
+ 2: {
133
+ icon: 'corporation',
134
+ },
135
+ };
136
+
137
+ const result = mapResult[item?.orgAttribute] || {};
138
+
139
+ return (
140
+ <div className="org-item-wrapper" style={{ width: '100%' }}>
141
+ <Ellipsis
142
+ title={item.name}
143
+ widthLimit={String(item.orgAttribute) === '1' ? 'calc(100% - 90px}' : '100%'}
144
+ />
145
+ {String(item.orgAttribute) === '1' && (
146
+ <span className="org-functional-departemt-marker">{result[lang] || '职能部门'}</span>
147
+ )}
148
+ </div>
149
+ );
150
+ };
151
+
152
+ return (
153
+ checkedKeys
154
+ .filter((i) => {
155
+ let node = orgMapRef.current[i] || {};
156
+ let { path, name } = node;
157
+ if (!path && !name) return false;
158
+ let pathDisplayName = getOrgPathDisplayName(path);
159
+
160
+ if (filterOrg) {
161
+ return (
162
+ pathDisplayName?.toLocaleLowerCase().includes(filterOrg?.toLocaleLowerCase()) ||
163
+ name?.toLocaleLowerCase().includes(filterOrg?.toLocaleLowerCase())
164
+ );
165
+ }
166
+ return i;
167
+ })
168
+ .map((item, index) => {
169
+ let node = orgMapRef.current[item] || {};
170
+ let { path, name } = node;
171
+
172
+ let pathDisplayName = getOrgPathDisplayName(path);
173
+ if (!path && !name) return null;
174
+ return (
175
+ <li key={item.value + index} className="select-menu-list-item">
176
+ <span className="org-name">
177
+ <Ellipsis title={renderOrgItem(node, props?.lang)} />
178
+ </span>
179
+ <span className="path-name">
180
+ <Ellipsis title={pathDisplayName} />
181
+ </span>
182
+ </li>
183
+ );
184
+ }) || <Empty />
185
+ );
186
+ }, [userList, checkedKeys, filterOrg]);
187
+ // 渲染App列表
188
+ let appListDomRender = useMemo(() => {
189
+ return (
190
+ appKeys
191
+ .filter((i) => {
192
+ let node = appMapRef.current[i] || {};
193
+ const { label, value } = node;
194
+ if (!value && !label) return false;
195
+
196
+ if (filterApp) {
197
+ return (
198
+ label?.toLocaleLowerCase().includes(filterApp?.toLocaleLowerCase()) ||
199
+ value?.toLocaleLowerCase().includes(filterApp?.toLocaleLowerCase())
200
+ );
201
+ }
202
+ return i;
203
+ })
204
+ .map((item, index) => {
205
+ let node = appMapRef.current[item] || {};
206
+ let { value, label } = node;
207
+
208
+ return (
209
+ <li key={value + index} className="select-menu-list-item">
210
+ <Tooltip title={label} key={label + index}>
211
+ <span className="app-name">{label}</span>
212
+ </Tooltip>
213
+ </li>
214
+ );
215
+ }) || <Empty />
216
+ );
217
+ }, [appList, appKeys, filterApp]);
218
+
219
+ // 渲染User列表
220
+ let userListDomRender = useMemo(() => {
221
+ return (
222
+ userKeys
223
+ ?.filter((item) => {
224
+ let node = userMapRef.current[item] || {};
225
+ const { account, userName } = node;
226
+ if (!userName && !node.account) return false;
227
+ if (filterUser) {
228
+ return (
229
+ account?.toLocaleLowerCase().includes(filterUser?.toLocaleLowerCase()) ||
230
+ userName?.toLocaleLowerCase().includes(filterUser?.toLocaleLowerCase())
231
+ );
232
+ }
233
+ return item;
234
+ })
235
+ .map((item, index) => {
236
+ let node = userMapRef.current[item] || {};
237
+ let { userName } = node;
238
+ return (
239
+ <li key={userName + index} className="select-menu-list-item">
240
+ <Tooltip title={userName} key={userName + index}>
241
+ <span className="user-name">{userName}</span>
242
+ </Tooltip>
243
+ </li>
244
+ );
245
+ }) || <Empty />
246
+ );
247
+ }, [userList, userKeys, filterUser]);
248
+
249
+ return (
250
+ <>
251
+ <div className="assign-box-container view-mode">
252
+ <div className="org-panel panel">
253
+ <div className="menu-header">
254
+ {/* 授权可用机构列表 */}
255
+ <span className="title">{orgTitle || t('authorizesOrgList')}</span>
256
+ </div>
257
+ <div className="panel-menu-body">
258
+ <ul className="select-menu-list">
259
+ <Input
260
+ size="small"
261
+ placeholder={t('search')}
262
+ onChange={(e) => {
263
+ debouncedOrgSearch(e.target.value);
264
+ }}
265
+ suffix={<Icon type="zoom" />}
266
+ style={{ marginBottom: 16 }}
267
+ />
268
+ {orgListDomRender}
269
+ </ul>
270
+ </div>
271
+ </div>
272
+ <div className="app-panel panel">
273
+ <div className="menu-header">
274
+ {/* 授权可用渠道列表 */}
275
+ <span className="title">{appTitle || t('authorizesAppList')}</span>
276
+ </div>
277
+ <div className="panel-menu-body">
278
+ <Input
279
+ onChange={(e) => {
280
+ debouncedAppSearch(e.target.value);
281
+ }}
282
+ placeholder={t('enterAppName')}
283
+ size="small"
284
+ suffix={<Icon type="zoom" />}
285
+ style={{ marginBottom: 16 }}
286
+ />
287
+ <ul className="select-menu-list">{appListDomRender}</ul>
288
+ </div>
289
+ </div>
290
+ {!!showUser && (
291
+ <div className="user-panel panel">
292
+ <div className="menu-header">
293
+ {/* 授权可用用户列表 */}
294
+ <span className="title">{userTitle || t('authorizesUserList')}</span>
295
+ </div>
296
+ <div className="panel-menu-body">
297
+ <Input
298
+ size="small"
299
+ placeholder={t('enterUserName')}
300
+ onChange={(e) => {
301
+ debouncedUserSearch(e.target.value);
302
+ }}
303
+ suffix={<Icon type="zoom" />}
304
+ style={{ marginBottom: 16 }}
305
+ />
306
+ <ul className="select-menu-list">{userListDomRender}</ul>
307
+ </div>
308
+ </div>
309
+ )}
310
+ </div>
311
+ </>
312
+ );
313
+ };
314
+
315
+ export default AssignModal;
@@ -0,0 +1,7 @@
1
+ import View from './View';
2
+ import Edit from './Edit';
3
+ const AssignModal = (props) => {
4
+ return props.disabled ? <View {...props} /> : <Edit {...props} />;
5
+ };
6
+
7
+ export default AssignModal;
@@ -0,0 +1,328 @@
1
+ .assign-box-container {
2
+ & {
3
+ height: calc(100% - 24px);
4
+ overflow: hidden;
5
+ }
6
+
7
+ .slider {
8
+ display: flex;
9
+ height: calc(100% - 24px);
10
+ margin-top: 16px;
11
+ // transition 已移至 inline style,支持动态面板数量
12
+
13
+ .panel {
14
+ flex: 1 0 0; // flex-grow: 1, flex-shrink: 0, flex-basis: 0
15
+ // 每个面板平分 slider 宽度,不压缩,从 0 基准开始计算
16
+ height: 100%;
17
+ padding: 16px;
18
+ overflow: hidden;
19
+ background-color: #fff;
20
+ border-radius: @border-radius-base * 2;
21
+ }
22
+
23
+ .panel-menu-body {
24
+ & {
25
+ display: flex;
26
+ width: 100%;
27
+ height: calc(100% - 36px);
28
+ padding: 16px 0 16px 16px;
29
+ background: @fill-color-quaternary;
30
+ border-radius: @border-radius-base;
31
+ }
32
+
33
+ > div {
34
+ width: 50%;
35
+ }
36
+
37
+ .panel-left {
38
+ height: 100%;
39
+ // overflow: auto;
40
+
41
+ > .ant-input-affix-wrapper {
42
+ position: sticky;
43
+ top: 0px;
44
+ z-index: 1;
45
+ // background: #f8f9fb;
46
+ }
47
+
48
+ > .ant-checkbox-wrapper {
49
+ margin-left: 8px;
50
+ }
51
+ }
52
+
53
+ .panel-right {
54
+ overflow: hidden;
55
+
56
+ & {
57
+ padding: 0 0px 16px 16px;
58
+ border-left: 1px solid @border-color-secondary;
59
+ }
60
+
61
+ .select-menu-header {
62
+ display: flex;
63
+ align-items: flex-start;
64
+ justify-content: space-between;
65
+ margin-right: 16px;
66
+ margin-bottom: 16px;
67
+ }
68
+
69
+ .select-menu-list {
70
+ position: relative;
71
+ height: calc(100% - 16px);
72
+ margin: 0px;
73
+ padding: 0px;
74
+ overflow: auto;
75
+
76
+ > .ant-input-affix-wrapper {
77
+ position: sticky;
78
+ top: 0px;
79
+ z-index: 1;
80
+ background: #f8f9fb;
81
+ }
82
+
83
+ .select-menu-list-item {
84
+ position: relative;
85
+ display: flex;
86
+ flex-direction: column;
87
+ height: 50px;
88
+ margin-right: 8px;
89
+ margin-bottom: 4px;
90
+ padding: 4px 16px 4px 8px;
91
+ border-radius: @border-radius-base;
92
+
93
+ .org-name {
94
+ display: inline-block;
95
+ color: @bg-color-spotilight;
96
+ font-weight: 400;
97
+ font-size: 14px;
98
+ font-family: 'PingFang SC';
99
+ line-height: 22px;
100
+ }
101
+
102
+ .path-name {
103
+ display: inline-block;
104
+ color: tint(@bg-color-spotilight, 50%);
105
+ font-weight: 400;
106
+ font-size: 12px;
107
+ font-family: 'PingFang SC';
108
+ line-height: 20px;
109
+ }
110
+
111
+ .close-icon {
112
+ position: absolute;
113
+ top: calc(50% - 6px);
114
+ right: 8px;
115
+ font-size: 12px;
116
+ cursor: pointer;
117
+ }
118
+ }
119
+
120
+ .select-menu-list-item:hover {
121
+ background-color: #fff;
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ .app-panel,
128
+ .user-panel {
129
+ .panel-menu-body {
130
+ .panel-left {
131
+ position: relative;
132
+ display: flex;
133
+ flex-direction: column;
134
+
135
+ > .ant-input-affix-wrapper {
136
+ position: sticky;
137
+ top: 0px;
138
+ z-index: 1;
139
+ background: #f8f9fb;
140
+ }
141
+
142
+ > label {
143
+ margin-bottom: 8px;
144
+ }
145
+
146
+ > label:last-child {
147
+ margin-bottom: 0px;
148
+ }
149
+ }
150
+
151
+ .panel-right {
152
+ .select-menu-list {
153
+ .select-menu-list-item {
154
+ height: 30px;
155
+ padding: 4px 8px;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ .menu-header {
164
+ & {
165
+ display: flex;
166
+ justify-content: space-between;
167
+ width: 100%;
168
+ margin-bottom: 16px;
169
+ }
170
+
171
+ .title {
172
+ position: relative;
173
+ margin-left: 12px;
174
+ color: @text-color;
175
+ font-weight: 600;
176
+ font-style: normal;
177
+ line-height: 22px;
178
+
179
+ .sum {
180
+ display: inline-block;
181
+ margin: 0 4px;
182
+ color: @blue-6;
183
+ }
184
+
185
+ .text-grey {
186
+ color: tint(@text-color, 50%);
187
+ }
188
+ }
189
+
190
+ .title::after {
191
+ position: absolute;
192
+ top: 5px;
193
+ left: -12px;
194
+ display: inline-block;
195
+ width: 4px;
196
+ height: 12px;
197
+ background: @blue-6;
198
+ border-radius: 4px;
199
+ content: '';
200
+ }
201
+ }
202
+
203
+ .disabeld {
204
+ color: rgba(0, 0, 0, 0.25);
205
+ cursor: not-allowed;
206
+ }
207
+ }
208
+
209
+ .assign-box-container.view-mode {
210
+ & {
211
+ display: flex;
212
+ gap: 16px;
213
+ height: 100%;
214
+ }
215
+
216
+ .panel.org-panel,
217
+ .panel.app-panel,
218
+ .panel.user-panel {
219
+ flex: 1;
220
+ height: 100%;
221
+ padding: 16px;
222
+ overflow: hidden;
223
+ background-color: #fff;
224
+ border-radius: @border-radius-base * 2;
225
+
226
+ .panel-menu-body {
227
+ & {
228
+ width: 100%;
229
+ height: calc(100% - 36px);
230
+ padding: 16px;
231
+ background: @fill-color-quaternary;
232
+ border-radius: @border-radius-base;
233
+ }
234
+
235
+ .select-menu-header {
236
+ display: flex;
237
+ align-items: flex-start;
238
+ justify-content: space-between;
239
+ margin-bottom: 16px;
240
+ }
241
+
242
+ .select-menu-list {
243
+ position: relative;
244
+ height: 100%;
245
+ margin: 0px;
246
+ padding: 0px;
247
+ overflow: auto;
248
+
249
+ > .ant-input-affix-wrapper {
250
+ position: sticky;
251
+ top: 0px;
252
+ z-index: 1;
253
+ background: #f8f9fb;
254
+ }
255
+
256
+ .select-menu-list-item {
257
+ &:last-child {
258
+ margin-bottom: 0px;
259
+ }
260
+
261
+ margin-bottom: 8px;
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ .org-panel {
268
+ flex: 1;
269
+
270
+ .panel-menu-body {
271
+ padding: 16px;
272
+ background: #f8f9fb;
273
+ border-radius: 8px;
274
+ // overflow: auto;
275
+ }
276
+
277
+ .select-menu-list {
278
+ position: relative;
279
+ height: calc(100% - 16px);
280
+ margin: 0px;
281
+ padding: 16px;
282
+
283
+ > .ant-input-affix-wrapper {
284
+ position: sticky;
285
+ top: 0px;
286
+ z-index: 1;
287
+ background: #f8f9fb;
288
+ }
289
+
290
+ .select-menu-list-item {
291
+ & {
292
+ position: relative;
293
+ display: flex;
294
+ flex-direction: column;
295
+ height: 50px;
296
+ margin-right: 16px;
297
+ margin-bottom: 4px !important;
298
+ padding: 0px;
299
+ border-radius: @border-radius-base;
300
+ }
301
+
302
+ &:last-child {
303
+ margin-bottom: 0px;
304
+ }
305
+
306
+ .org-name {
307
+ display: inline-block;
308
+ height: 22px;
309
+ color: @bg-color-spotilight;
310
+ font-weight: 400;
311
+ font-size: 14px;
312
+ font-family: 'PingFang SC';
313
+ line-height: 22px;
314
+ }
315
+
316
+ .path-name {
317
+ display: inline-block;
318
+ height: 22px;
319
+ color: tint(@bg-color-spotilight, 50%);
320
+ font-weight: 400;
321
+ font-size: 12px;
322
+ font-family: 'PingFang SC';
323
+ line-height: 22px;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }