@spacecorexdev/core 0.0.4 → 0.0.5

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.
Files changed (132) hide show
  1. package/package.json +2 -36
  2. package/src/api/_base.ts +243 -0
  3. package/src/api/index.ts +17 -0
  4. package/src/api/project.ts +1345 -0
  5. package/src/api/user.ts +1174 -0
  6. package/src/contract.ts +50 -0
  7. package/src/hooks/index.ts +2 -0
  8. package/{dist/hooks/index.js → src/hooks/useLongPress.ts} +53 -49
  9. package/src/hooks/useOnlineStatus.ts +22 -0
  10. package/src/index.ts +14 -0
  11. package/src/types/id.ts +2 -0
  12. package/src/types/index.ts +5 -0
  13. package/src/types/project/base.ts +63 -0
  14. package/src/types/project/contract.ts +79 -0
  15. package/src/types/project/contractStatus.tsx +15 -0
  16. package/src/types/project/index.ts +5 -0
  17. package/src/types/project/member.ts +46 -0
  18. package/src/types/project/progress.ts +43 -0
  19. package/src/types/projectRole.ts +17 -0
  20. package/src/types/theme.ts +1 -0
  21. package/src/types/user/index.ts +1 -0
  22. package/src/types/user/role.ts +7 -0
  23. package/src/ui/CommentTimeline/index.tsx +330 -0
  24. package/src/ui/FileManager/FileContextMenu.tsx +628 -0
  25. package/src/ui/FileManager/FileGridView.tsx +529 -0
  26. package/src/ui/FileManager/FileTableView.tsx +405 -0
  27. package/src/ui/FileManager/FileToolbar.tsx +119 -0
  28. package/src/ui/FileManager/FileVersionHistoryModal.tsx +175 -0
  29. package/src/ui/FileManager/MoveCopyToProjectModal.tsx +119 -0
  30. package/src/ui/FileManager/ShareToWechatModal.tsx +419 -0
  31. package/src/ui/FileManager/hooks/useFileManagerActions.ts +2532 -0
  32. package/src/ui/FileManager/hooks/useFileManagerState.ts +419 -0
  33. package/src/ui/FileManager/index.tsx +580 -0
  34. package/src/ui/FileManager/projectFileManagerApi.ts +57 -0
  35. package/src/ui/FileManager/types.ts +184 -0
  36. package/src/ui/FileManager/utils/fileDeltaWorker.ts +193 -0
  37. package/src/ui/FileManager/utils/fileTree.ts +178 -0
  38. package/src/ui/FileManager/utils/fileUpload.ts +714 -0
  39. package/src/ui/FileManager/utils/fileUtils.ts +65 -0
  40. package/src/ui/InvoiceRecordsList/index.tsx +813 -0
  41. package/src/ui/MyFileSelectorModal.tsx +261 -0
  42. package/src/ui/ProjectSelector/index.tsx +472 -0
  43. package/src/ui/ProjectSelector/projectCache.ts +40 -0
  44. package/src/ui/RoleSelector/index.tsx +473 -0
  45. package/src/ui/RoleSelector/roleCache.ts +40 -0
  46. package/src/ui/UIAlert.tsx +399 -0
  47. package/src/ui/UIAlertExample.tsx +129 -0
  48. package/src/ui/UIConfirm.tsx +335 -0
  49. package/src/ui/UIConfirmExample.tsx +165 -0
  50. package/src/ui/UIDatePicker.tsx +146 -0
  51. package/src/ui/UIDateRangePicker.tsx +38 -0
  52. package/src/ui/UIFilePreview/ExcelEditor/index.tsx +617 -0
  53. package/src/ui/UIFilePreview/ExcelEditor/types.ts +24 -0
  54. package/src/ui/UIFilePreview/ExportDropdown.css +85 -0
  55. package/src/ui/UIFilePreview/ExportDropdown.tsx +109 -0
  56. package/src/ui/UIFilePreview/IndexedDBUtils.ts +564 -0
  57. package/src/ui/UIFilePreview/MarkdownEditor/exportMarkdownPdf.ts +576 -0
  58. package/src/ui/UIFilePreview/MarkdownEditor/index.tsx +677 -0
  59. package/src/ui/UIFilePreview/MarkdownEditor/markdownEditor.css +1005 -0
  60. package/src/ui/UIFilePreview/MarkdownEditor/types.ts +15 -0
  61. package/src/ui/UIFilePreview/PPTXEditor/index.tsx +602 -0
  62. package/src/ui/UIFilePreview/RichDocEditor/exportRDocMarkdown.ts +77 -0
  63. package/src/ui/UIFilePreview/RichDocEditor/exportRDocPdf.ts +374 -0
  64. package/src/ui/UIFilePreview/RichDocEditor/index.tsx +730 -0
  65. package/src/ui/UIFilePreview/RichDocEditor/richdoc-editor.css +59 -0
  66. package/src/ui/UIFilePreview/UIFilePreview.tsx +611 -0
  67. package/src/ui/UIFilePreview/WordEditor/index.tsx +3933 -0
  68. package/src/ui/UIFilePreview/WordEditor/style.ts +1213 -0
  69. package/src/ui/UIFilePreview/WordEditor/word-editor.css +999 -0
  70. package/src/ui/UIFilePreview/filePreview.css +193 -0
  71. package/src/ui/UIFilePreview/filetypes/archive.tsx +66 -0
  72. package/src/ui/UIFilePreview/filetypes/cad.tsx +287 -0
  73. package/src/ui/UIFilePreview/filetypes/excel.tsx +355 -0
  74. package/src/ui/UIFilePreview/filetypes/image.tsx +20 -0
  75. package/src/ui/UIFilePreview/filetypes/imageLoader.ts +63 -0
  76. package/src/ui/UIFilePreview/filetypes/pdf.tsx +156 -0
  77. package/src/ui/UIFilePreview/filetypes/ppt.tsx +535 -0
  78. package/src/ui/UIFilePreview/filetypes/pptExportPdf.ts +523 -0
  79. package/src/ui/UIFilePreview/filetypes/video.tsx +482 -0
  80. package/src/ui/UIFilePreview/filetypes/word.tsx +245 -0
  81. package/src/ui/UIFilePreview/filetypes/wordExportMarkdown.ts +86 -0
  82. package/src/ui/UIFilePreview/filetypes/wordExportPdf.tsx +427 -0
  83. package/src/ui/UIFilePreview/index.ts +35 -0
  84. package/src/ui/UIFilePreview/pdfPagination.ts +143 -0
  85. package/src/ui/UIFilePreview/previewUtils.tsx +531 -0
  86. package/src/ui/UIFileUpload.tsx +382 -0
  87. package/src/ui/UIImageUpload.tsx +195 -0
  88. package/src/ui/UILoading.tsx +255 -0
  89. package/src/ui/UILoadingExample.tsx +139 -0
  90. package/src/ui/UISelect.tsx +105 -0
  91. package/src/ui/UIToast.tsx +381 -0
  92. package/src/ui/UIToastExample.tsx +69 -0
  93. package/src/ui/UIToastSingle.tsx +279 -0
  94. package/src/ui/UIViewToggle.tsx +64 -0
  95. package/src/ui/UserSelector/index.tsx +583 -0
  96. package/src/ui/index.ts +84 -0
  97. package/src/ui/upload/image.ts +135 -0
  98. package/src/ui/upload/index.ts +73 -0
  99. package/src/ui/upload/submitFile.ts +106 -0
  100. package/src/ui/upload/submitForm.ts +124 -0
  101. package/src/ui/upload/upload.err.ts +15 -0
  102. package/src/ui/upload/upload.ts +250 -0
  103. package/src/utils/CacheDepartmentInfo.ts +372 -0
  104. package/src/utils/CacheFileContent.ts +315 -0
  105. package/src/utils/CachePermissionInfo.ts +235 -0
  106. package/src/utils/CacheRoleInfo.ts +303 -0
  107. package/src/utils/CacheUserInfo.ts +818 -0
  108. package/src/utils/CacheViewToggleInfo.ts +182 -0
  109. package/src/utils/MessageContentUtils.ts +268 -0
  110. package/src/utils/clipboard.ts +70 -0
  111. package/src/utils/companyEmail.ts +19 -0
  112. package/src/utils/forbidDebugger.ts +59 -0
  113. package/src/utils/formatters.ts +94 -0
  114. package/src/utils/htmlUtils.tsx +59 -0
  115. package/src/utils/index.ts +18 -0
  116. package/src/utils/keyboard.ts +114 -0
  117. package/src/utils/mammothApi.ts +16 -0
  118. package/src/utils/polyfills.ts +928 -0
  119. package/src/utils/regex.ts +14 -0
  120. package/src/utils/storage.ts +95 -0
  121. package/src/utils/workerBootstrap.ts +87 -0
  122. package/dist/api/index.js +0 -1553
  123. package/dist/contract.js +0 -5
  124. package/dist/index.js +0 -4492
  125. package/dist/types/index.js +0 -49
  126. package/dist/ui/index.css +0 -1876
  127. package/dist/ui/index.js +0 -22245
  128. package/dist/utils/index.js +0 -4296
  129. /package/{dist → src}/styles/ant-modal.css +0 -0
  130. /package/{dist → src}/styles/ant-spin.css +0 -0
  131. /package/{dist → src}/styles/ant-table.css +0 -0
  132. /package/{dist → src}/styles/index.css +0 -0
@@ -0,0 +1,473 @@
1
+ import React, { useEffect, useState, useCallback, useRef, useMemo, ReactElement } from 'react';
2
+ import { Select, Avatar, Spin, message, Popover, Card, Typography, Space } from 'antd';
3
+ import { debounce } from 'lodash-es';
4
+ import type { SelectProps } from 'antd/es/select';
5
+ import { ResponseCode } from '../../api/_base';
6
+ import ReactDOM from 'react-dom/client';
7
+ import { Role } from '../../types/user/role';
8
+ import { API } from '../../api/index';
9
+ import { CacheRoleInfo } from '../../utils/CacheRoleInfo';
10
+ import { roleCacheManager } from './roleCache';
11
+
12
+ /******
13
+ 使用示例:
14
+
15
+ <RoleSelector
16
+ value={selectedRoleIds}
17
+ onChange={(roleIds, roles) => {
18
+ setSelectedRoleIds(roleIds);
19
+ }}`
20
+ placeholder="请选择角色"
21
+ multiple={false}
22
+ excludeRoleIds={[]}
23
+ />
24
+
25
+ ****/
26
+
27
+
28
+ const { Text } = Typography;
29
+
30
+ export interface RoleSelectorProps extends Omit<SelectProps, 'options' | 'value' | 'onChange'> {
31
+ // 是否允许多选
32
+ multiple?: boolean;
33
+ // 需要排除的角色ID列表
34
+ excludeRoleIds?: string[];
35
+ // 是否允许清除选择
36
+ allowClear?: boolean;
37
+ // 请求超时时间(毫秒)
38
+ timeout?: number;
39
+ // 请求重试次数
40
+ retryCount?: number;
41
+ // 是否启用缓存
42
+ enableCache?: boolean;
43
+ // 缓存过期时间(毫秒)
44
+ cacheExpireTime?: number;
45
+ // 选中的角色ID数组
46
+ value?: string[];
47
+ // 选中角色变化时的回调
48
+ onChange?: (roleIds: string[], selectedRoles: Role[]) => void;
49
+ }
50
+
51
+ const RoleSelector: React.FC<RoleSelectorProps> = ({
52
+ multiple = false,
53
+ allowClear = true,
54
+ timeout = 10000,
55
+ retryCount = 3,
56
+ enableCache = true,
57
+ excludeRoleIds = [],
58
+ cacheExpireTime,
59
+ value,
60
+ onChange,
61
+ ...props
62
+ }) => {
63
+ const [loading, setLoading] = useState(false);
64
+ const [options, setOptions] = useState<Role[]>([]);
65
+ const [searchValue, setSearchValue] = useState('');
66
+ const [pageNum, setPageNum] = useState(1);
67
+ const [hasMore, setHasMore] = useState(true);
68
+ const [selectedRoles, setSelectedRoles] = useState<Role[]>([]);
69
+ const abortControllerRef = useRef<AbortController | null>(null);
70
+ const optionsRef = useRef<Role[]>([]);
71
+ const selectedRolesRef = useRef<Role[]>([]);
72
+
73
+ // 更新 refs
74
+ useEffect(() => {
75
+ optionsRef.current = options;
76
+ }, [options]);
77
+
78
+ useEffect(() => {
79
+ selectedRolesRef.current = selectedRoles;
80
+ }, [selectedRoles]);
81
+
82
+ // 根据选中的用户ID获取完整的用户信息
83
+ const getSelectedRolesInfo = useCallback((userIds: string[]) => {
84
+ return userIds.map(id => {
85
+ // 先从已加载的选项中查找
86
+ const found = optionsRef.current.find(user => user.id === id);
87
+ if (found) return found;
88
+
89
+ // 如果选项中没有,则从已选中的用户中查找
90
+ return selectedRolesRef.current.find(user => user.id === id);
91
+ }).filter((user): user is Role => user !== undefined);
92
+ }, []); // 移除 options 和 selectedRoles 依赖
93
+
94
+ // 处理选择变化
95
+ const handleChange = useCallback((newValue: string | string[] | null) => {
96
+ if (!newValue) {
97
+ setSelectedRoles([]);
98
+ onChange?.([], []);
99
+ return;
100
+ }
101
+
102
+ // 根据模式处理值
103
+ const userIds = multiple
104
+ ? (Array.isArray(newValue) ? newValue : [newValue])
105
+ : (typeof newValue === 'string' ? [newValue] : newValue);
106
+
107
+ if (userIds.length === 0) {
108
+ setSelectedRoles([]);
109
+ onChange?.([], []);
110
+ return;
111
+ }
112
+
113
+ const users = getSelectedRolesInfo(userIds);
114
+ setSelectedRoles(users);
115
+ onChange?.(userIds, users);
116
+ }, [getSelectedRolesInfo, onChange, multiple]);
117
+
118
+ // 当外部 value 变化时更新选中状态
119
+ useEffect(() => {
120
+ if (!value || value.length == 0) {
121
+ setSelectedRoles([]);
122
+ return;
123
+ }
124
+
125
+ // 根据模式处理值
126
+ const userIds = multiple
127
+ ? (Array.isArray(value) ? value : [])
128
+ : (typeof value === 'string' ? [value] : []);
129
+
130
+ const users = getSelectedRolesInfo(userIds);
131
+
132
+ if (selectedRoles.length == users.length) {
133
+ let i = 0;
134
+ for (i = 0; i < users.length; i++) {
135
+ if (selectedRoles.find((value) => { return value.id == users[i].id ? value : null; })) {
136
+ break;
137
+ }
138
+ }
139
+ if (i < users.length) {
140
+ return;
141
+ }
142
+ } // if.
143
+
144
+ setSelectedRoles(users);
145
+ // eslint-disable-next-line react-hooks/exhaustive-deps
146
+ }, [value, getSelectedRolesInfo, multiple]);
147
+
148
+ // 带重试的请求函数
149
+ const fetchWithRetry = useCallback(async (fn: () => Promise<any>, retries: number = retryCount): Promise<any> => {
150
+ try {
151
+ // 创建新的 AbortController
152
+ abortControllerRef.current = new AbortController();
153
+ const signal = abortControllerRef.current.signal;
154
+
155
+ // 设置超时
156
+ const timeoutPromise = new Promise((_, reject) => {
157
+ setTimeout(() => {
158
+ reject(new Error('请求超时'));
159
+ abortControllerRef.current?.abort();
160
+ }, timeout);
161
+ });
162
+
163
+ // 执行请求
164
+ const result = await Promise.race([
165
+ fn(),
166
+ timeoutPromise,
167
+ ]);
168
+
169
+ return result;
170
+ } catch (error) {
171
+ if (retries > 0 && !(error instanceof Error && error.name === 'AbortError')) {
172
+ // 如果不是取消请求,则重试
173
+ await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试
174
+ return fetchWithRetry(fn, retries - 1);
175
+ }
176
+ throw error;
177
+ }
178
+ }, [timeout, retryCount]);
179
+
180
+ // 获角色列表
181
+ const fetchRoles = useCallback(async (search: string, append: boolean = false) => {
182
+ try {
183
+ setLoading(true);
184
+
185
+ // 生成缓存key
186
+ const cacheKey = `roles_${search}`;
187
+
188
+ // 检查缓存
189
+ if (enableCache && !search) {
190
+ const cachedData = roleCacheManager.get(cacheKey);
191
+ if (cachedData) {
192
+ setOptions(prev => append ? [...prev, ...cachedData] : cachedData);
193
+ setHasMore(false);
194
+ return;
195
+ }
196
+ }
197
+
198
+ const res = await fetchWithRetry(async () => {
199
+ const result = await CacheRoleInfo.getRoleList({ forceRefresh: true }) || [];
200
+ return result;
201
+ });
202
+
203
+ const filteredUsers = res.filter((user: Role) => {
204
+ // 过滤排除的用户
205
+ if (excludeRoleIds.includes(user.id)) {
206
+ return false;
207
+ }
208
+ return true;
209
+ });
210
+
211
+ // 更新缓存
212
+ if (enableCache && !search) {
213
+ roleCacheManager.set(cacheKey, filteredUsers, cacheExpireTime);
214
+ }
215
+
216
+ setOptions(prev => append ? [...prev, ...filteredUsers] : filteredUsers);
217
+ setHasMore(false);
218
+ } catch (error) {
219
+ console.error('获取角色列表失败:', error);
220
+ message.error('获取角色列表失败,请稍后重试');
221
+ } finally {
222
+ setLoading(false);
223
+ }
224
+ }, [excludeRoleIds, fetchWithRetry, enableCache, cacheExpireTime]);
225
+
226
+ // 组件卸载时取消未完成的请求
227
+ useEffect(() => {
228
+ return () => {
229
+ abortControllerRef.current?.abort();
230
+ };
231
+ }, []);
232
+
233
+ // 搜索防抖
234
+ const debouncedSearch = useMemo(
235
+ () => debounce((value: string) => {
236
+ setSearchValue(value);
237
+ setPageNum(1);
238
+ fetchRoles(value, false);
239
+ }, 300),
240
+ [fetchRoles]
241
+ );
242
+
243
+ // 滚动加载更多
244
+ const handlePopupScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
245
+ const { target } = e;
246
+ if (!target) return;
247
+
248
+ const { scrollTop, scrollHeight, clientHeight } = target as HTMLDivElement;
249
+ if (scrollHeight - scrollTop <= clientHeight * 1.5 && !loading && hasMore) {
250
+ const nextPage = pageNum + 1;
251
+ setPageNum(nextPage);
252
+ fetchRoles(searchValue, true);
253
+ }
254
+ }, [loading, hasMore, pageNum, searchValue, fetchRoles]);
255
+
256
+ // 初始加载
257
+ useEffect(() => {
258
+ fetchRoles('', false);
259
+ }, [fetchRoles]);
260
+
261
+ // 渲染用户信息卡片
262
+ const renderRoleInfoCard = (role: Role) => {
263
+ return (
264
+ <Card size="small" style={{ width: 280 }}>
265
+ <Space direction="vertical" size="small" style={{ width: '100%' }}>
266
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
267
+ <div>
268
+ <div style={{ fontWeight: 'bold' }}>{role.name}</div>
269
+ </div>
270
+ </div>
271
+ </Space>
272
+ </Card>
273
+ );
274
+ };
275
+
276
+ // 渲染选项
277
+ const renderOption = (user: Role) => {
278
+ const parts = [];
279
+ parts.push(
280
+ <span key="name" style={{
281
+ verticalAlign: 'middle',
282
+ lineHeight: '20px'
283
+ }}>
284
+ {user.name}
285
+ </span>
286
+ );
287
+
288
+ return (
289
+ <div style={{
290
+ display: 'flex',
291
+ alignItems: 'center',
292
+ minHeight: '24px'
293
+ }}>
294
+ {parts}
295
+ </div>
296
+ );
297
+ };
298
+
299
+ // 使用 useMemo 缓存选项数据
300
+ const selectOptions = useMemo(() =>
301
+ options.map(user => ({
302
+ label: renderOption(user),
303
+ value: user.id,
304
+ user, // 将用户信息存储在选项中
305
+ })),
306
+ [options]
307
+ );
308
+
309
+ // 自定义标签渲染
310
+ const customTagRender = useCallback((props: any): ReactElement => {
311
+ const { value, closable, onClose } = props;
312
+ const user = options.find(u => u.id === value) || selectedRoles.find(u => u.id === value);
313
+
314
+ if (!user) {
315
+ return <div>{value}</div>;
316
+ }
317
+
318
+ return (
319
+ <Popover
320
+ content={renderRoleInfoCard(user)}
321
+ trigger="click"
322
+ placement="right"
323
+ >
324
+ <div style={{
325
+ display: 'flex',
326
+ alignItems: 'center',
327
+ cursor: 'pointer',
328
+ padding: '2px 8px',
329
+ background: 'rgb(var(--color-gray-100))',
330
+ borderRadius: 4,
331
+ margin: '2px 4px',
332
+ border: '1px solid rgb(var(--color-border))'
333
+ }}>
334
+ <span style={{ marginRight: 8 }}>{user.name}</span>
335
+ {closable && (
336
+ <span
337
+ onClick={onClose}
338
+ style={{
339
+ cursor: 'pointer',
340
+ color: 'rgb(var(--color-gray-400))',
341
+ fontSize: 12,
342
+ padding: '0 4px'
343
+ }}
344
+ >
345
+ ×
346
+ </span>
347
+ )}
348
+ </div>
349
+ </Popover>
350
+ );
351
+ }, [options, selectedRoles]);
352
+
353
+ // 自定义选中值的渲染
354
+ const customSingleValueRender = useCallback((value: string): ReactElement => {
355
+ const user = options.find(u => u.id === value) || selectedRoles.find(u => u.id === value);
356
+
357
+ if (!user) {
358
+ return <div>{value}</div>;
359
+ }
360
+
361
+ return (
362
+ <Popover
363
+ content={renderRoleInfoCard(user)}
364
+ trigger="click"
365
+ placement="right"
366
+ >
367
+ <div style={{
368
+ display: 'flex',
369
+ alignItems: 'center',
370
+ cursor: 'pointer',
371
+ height: '100%'
372
+ }}>
373
+ <span>{user.name}</span>
374
+ </div>
375
+ </Popover>
376
+ );
377
+ }, [options, selectedRoles]);
378
+
379
+ // 获取当前选中的用户
380
+ const getCurrentRole = useCallback((currentValue: string | string[] | null | undefined) => {
381
+ if (!currentValue) return null;
382
+ const userId = Array.isArray(currentValue) ? currentValue[0] : currentValue;
383
+ return options.find(u => u.id === userId) || selectedRoles.find(u => u.id === userId);
384
+ }, [options, selectedRoles]);
385
+
386
+ return (
387
+ <Select
388
+ {...props}
389
+ mode={multiple ? 'multiple' : undefined}
390
+ showSearch
391
+ loading={loading}
392
+ filterOption={false}
393
+ onSearch={debouncedSearch}
394
+ onPopupScroll={handlePopupScroll}
395
+ notFoundContent={loading ? <Spin size="small" /> : null}
396
+ allowClear={allowClear}
397
+ value={multiple ? value : (typeof value === 'string' ? value : value?.toString())}
398
+ onChange={handleChange}
399
+ options={selectOptions}
400
+ style={{
401
+ ...props.style,
402
+ minHeight: '32px'
403
+ }}
404
+ tagRender={multiple ? customTagRender : undefined}
405
+ fieldNames={{
406
+ label: 'label',
407
+ value: 'value',
408
+ }}
409
+ // 自定义选中值的渲染
410
+ popupRender={multiple ? undefined : (menu) => {
411
+ const currentUser = getCurrentRole(value);
412
+ if (!currentUser) return menu;
413
+
414
+ return (
415
+ <div>
416
+ <div style={{
417
+ padding: '8px 12px',
418
+ borderBottom: '1px solid rgb(var(--color-border))',
419
+ cursor: 'pointer'
420
+ }}>
421
+ {customSingleValueRender(currentUser.id)}
422
+ </div>
423
+ {menu}
424
+ </div>
425
+ );
426
+ }}
427
+ // 自定义选中值的显示
428
+ suffixIcon={multiple ? undefined : (
429
+ value ? (
430
+ <div
431
+ onClick={(e) => {
432
+ e.stopPropagation();
433
+ const currentUser = getCurrentRole(value);
434
+ if (currentUser) {
435
+ // 创建一个临时的 Popover 实例
436
+ const popover = document.createElement('div');
437
+ document.body.appendChild(popover);
438
+ const root = ReactDOM.createRoot(popover);
439
+ root.render(
440
+ <Popover
441
+ content={renderRoleInfoCard(currentUser)}
442
+ trigger="click"
443
+ placement="right"
444
+ open={true}
445
+ onOpenChange={(visible) => {
446
+ if (!visible) {
447
+ root.unmount();
448
+ document.body.removeChild(popover);
449
+ }
450
+ }}
451
+ >
452
+ <div style={{ position: 'absolute', left: 0, top: 0, width: '100%', height: '100%' }} />
453
+ </Popover>
454
+ );
455
+ }
456
+ }}
457
+ style={{
458
+ cursor: 'pointer',
459
+ padding: '0 8px',
460
+ height: '100%',
461
+ display: 'flex',
462
+ alignItems: 'center'
463
+ }}
464
+ >
465
+ <span className="ant-select-arrow" />
466
+ </div>
467
+ ) : <span className="ant-select-arrow" />
468
+ )}
469
+ />
470
+ );
471
+ };
472
+
473
+ export default RoleSelector;
@@ -0,0 +1,40 @@
1
+ interface CacheItem {
2
+ data: any;
3
+ timestamp: number;
4
+ expireTime: number;
5
+ }
6
+
7
+ class CacheManager {
8
+ private cache: Map<string, CacheItem> = new Map();
9
+ private readonly defaultExpireTime = 5 * 60 * 1000;
10
+
11
+ set(key: string, data: any, expireTime: number = this.defaultExpireTime) {
12
+ this.cache.set(key, {
13
+ data,
14
+ timestamp: Date.now(),
15
+ expireTime,
16
+ });
17
+ }
18
+
19
+ get(key: string): any | null {
20
+ const item = this.cache.get(key);
21
+ if (!item) return null;
22
+
23
+ if (Date.now() - item.timestamp > item.expireTime) {
24
+ this.cache.delete(key);
25
+ return null;
26
+ }
27
+
28
+ return item.data;
29
+ }
30
+
31
+ clear() {
32
+ this.cache.clear();
33
+ }
34
+ }
35
+
36
+ export const roleCacheManager = new CacheManager();
37
+
38
+ export function clearRoleCache() {
39
+ roleCacheManager.clear();
40
+ }