@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,628 @@
1
+ import React, { useState, useRef, useLayoutEffect, memo } from 'react';
2
+ import { Dropdown, Button, MenuProps, Menu } from 'antd';
3
+ import { DownloadOutlined, DeleteOutlined, EditOutlined, InfoCircleOutlined, MoreOutlined, CopyOutlined, ExportOutlined, FolderAddOutlined, HistoryOutlined, LinkOutlined, CommentOutlined, ClearOutlined, ArrowUpOutlined, FileAddOutlined } from '@ant-design/icons';
4
+ import { ProjectFile } from './types';
5
+ import { useEffectResize } from '../../utils/htmlUtils';
6
+ import { ShareAltOutlined } from '@ant-design/icons';
7
+ import { isEditableFileType, CONTRACTS_VIRTUAL_FOLDER_ID } from './utils/fileUtils';
8
+
9
+ interface FileContextMenuProps {
10
+ record: ProjectFile;
11
+ onPreview: (file: ProjectFile) => void;
12
+ onDownload: (file: ProjectFile) => void;
13
+ /** 下载文件夹回调(仅管理员/负责人提供;不提供时隐藏「下载文件夹」) */
14
+ onDownloadFolder?: (file: ProjectFile) => void;
15
+ /** 复制链接回调(未提供时不显示「复制链接」) */
16
+ onCopyLink?: (file: ProjectFile) => void;
17
+ onRename: (file: ProjectFile) => void;
18
+ onEditDescs: (file: ProjectFile) => void;
19
+ onDelete: (fileId: string) => void;
20
+ onEdit?: (file: ProjectFile) => void;
21
+ /** 生成临时访问链接回调 */
22
+ onGenerateTempLink?: (file: ProjectFile) => void;
23
+ /** 移动到其他项目回调 */
24
+ onMoveToProject?: (file: ProjectFile) => void;
25
+ /** 复制到其他项目回调 */
26
+ onCopyToProject?: (file: ProjectFile) => void;
27
+ /** 复制副本回调 */
28
+ onCopyToSelf?: (file: ProjectFile) => void;
29
+ /** 查看文件历史版本回调 */
30
+ onViewVersionHistory?: (file: ProjectFile) => void;
31
+ /** 分享到微信回调 */
32
+ onShareToWechat?: (file: ProjectFile) => void;
33
+ /** 清理缓存回调 */
34
+ onClearCache?: (file: ProjectFile) => void;
35
+ /** 在新窗口打开回调 */
36
+ onOpenInNewWindow?: (file: ProjectFile) => void;
37
+ /** 跳转至合同回调 */
38
+ onJumpToContract?: (file: ProjectFile) => void;
39
+ /** 创建合同回调(仅合同根文件夹显示) */
40
+ onCreateContract?: (file: ProjectFile) => void;
41
+ /** 是否为管理员或负责人 */
42
+ isAdminOrOwner?: boolean;
43
+ trigger?: 'click' | 'hover' | 'contextMenu';
44
+ placement?: 'top' | 'bottom' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
45
+ /** 手动控制显示状态(用于右键菜单) */
46
+ open?: boolean;
47
+ /** 菜单位置(用于右键菜单) */
48
+ position?: { x: number; y: number };
49
+ /** 自定义样式类名 */
50
+ className?: string;
51
+ }
52
+
53
+ const FileContextMenuComponent: React.FC<FileContextMenuProps> = ({
54
+ record,
55
+ onPreview,
56
+ onDownload,
57
+ onDownloadFolder,
58
+ onCopyLink,
59
+ onRename,
60
+ onEditDescs,
61
+ onDelete,
62
+ onEdit,
63
+ onGenerateTempLink,
64
+ onMoveToProject,
65
+ onCopyToProject,
66
+ onCopyToSelf,
67
+ onViewVersionHistory,
68
+ onShareToWechat,
69
+ onClearCache,
70
+ onOpenInNewWindow,
71
+ onJumpToContract,
72
+ onCreateContract,
73
+ isAdminOrOwner = false,
74
+ trigger = 'click',
75
+ placement = 'bottomRight',
76
+ open,
77
+ position,
78
+ className,
79
+ }) => {
80
+
81
+ const [isMobile, setIsMobile] = useState(false);
82
+
83
+ useEffectResize(() => {
84
+ setIsMobile(true);
85
+ }, () => {
86
+ setIsMobile(false);
87
+ });
88
+
89
+ const menuItems: MenuProps['items'] = [];
90
+
91
+ // 移动/复制到其他项目的子菜单(按回调存在与否动态生成)
92
+ const moveCopyToProjectMenu: MenuProps['items'] = [];
93
+ if (onCopyToSelf) {
94
+ moveCopyToProjectMenu.push({
95
+ key: 'copyToSelf',
96
+ label: '复制副本',
97
+ icon: <FolderAddOutlined />,
98
+ onClick: (e: any) => {
99
+ onCopyToSelf(record);
100
+ e.domEvent.stopPropagation();
101
+ },
102
+ });
103
+ }
104
+ if (onCopyToProject) {
105
+ moveCopyToProjectMenu.push({
106
+ key: 'copyToProject',
107
+ label: '复制到其他项目',
108
+ icon: <FolderAddOutlined />,
109
+ onClick: (e: any) => {
110
+ onCopyToProject(record);
111
+ e.domEvent.stopPropagation();
112
+ },
113
+ });
114
+ }
115
+ if (onMoveToProject) {
116
+ moveCopyToProjectMenu.push({
117
+ key: 'moveToProject',
118
+ label: '移动到其他项目',
119
+ icon: <ExportOutlined />,
120
+ onClick: (e: any) => {
121
+ onMoveToProject(record);
122
+ e.domEvent.stopPropagation();
123
+ },
124
+ });
125
+ }
126
+
127
+ // 合同附件链接文件:仅支持预览和下载
128
+ if (record.isContractLink) {
129
+ menuItems.push(
130
+ {
131
+ key: 'preview',
132
+ label: '预览',
133
+ icon: <InfoCircleOutlined />,
134
+ onClick: (e: any) => {
135
+ onPreview(record);
136
+ e.domEvent.stopPropagation();
137
+ },
138
+ },
139
+ {
140
+ key: 'download',
141
+ label: '下载',
142
+ icon: <DownloadOutlined />,
143
+ onClick: (e: any) => {
144
+ onDownload(record);
145
+ e.domEvent.stopPropagation();
146
+ },
147
+ },
148
+ );
149
+ if (onJumpToContract) {
150
+ menuItems.push({
151
+ key: 'jumpToContract',
152
+ label: '跳转至合同',
153
+ icon: <ExportOutlined />,
154
+ onClick: (e: any) => {
155
+ onJumpToContract(record);
156
+ e.domEvent.stopPropagation();
157
+ },
158
+ });
159
+ }
160
+ } else if (record.isVirtualContractFolder) {
161
+ // 虚拟合同文件夹:不允许重命名/删除/移动
162
+ // 合同根文件夹(含纯虚拟与真实记录)均提供"创建合同"入口
163
+ if (onCreateContract) {
164
+ menuItems.push({
165
+ key: 'createContract',
166
+ label: '创建合同',
167
+ icon: <FileAddOutlined />,
168
+ onClick: (e: any) => {
169
+ onCreateContract(record);
170
+ e.domEvent.stopPropagation();
171
+ },
172
+ });
173
+ }
174
+
175
+ if (record.id !== CONTRACTS_VIRTUAL_FOLDER_ID) {
176
+ // 真实文件夹(由用户创建的 __contracts__):允许编辑备注和临时访问链接
177
+ menuItems.push({
178
+ key: 'editDescs',
179
+ label: '编辑备注',
180
+ icon: <CommentOutlined />,
181
+ onClick: (e: any) => {
182
+ onEditDescs(record);
183
+ e.domEvent.stopPropagation();
184
+ },
185
+ });
186
+
187
+ if (onGenerateTempLink) {
188
+ menuItems.push({
189
+ key: 'generateTempLink',
190
+ label: '临时访问链接',
191
+ icon: <LinkOutlined />,
192
+ onClick: (e: any) => {
193
+ onGenerateTempLink(record);
194
+ e.domEvent.stopPropagation();
195
+ },
196
+ });
197
+ }
198
+ } else if (!onCreateContract) {
199
+ // 纯虚拟文件夹(无真实服务器记录)且未提供创建合同回调:仅显示提示
200
+ menuItems.push({
201
+ key: 'contractFolderHint',
202
+ label: '合同附件文件夹',
203
+ icon: <InfoCircleOutlined />,
204
+ disabled: true,
205
+ });
206
+ }
207
+ } else if (record.isContractSubfolder) {
208
+ // 合同子文件夹:仅支持跳转至合同,不允许重命名/删除/移动
209
+ if (onJumpToContract) {
210
+ menuItems.push({
211
+ key: 'jumpToContract',
212
+ label: '跳转至合同',
213
+ icon: <ExportOutlined />,
214
+ onClick: (e: any) => {
215
+ onJumpToContract(record);
216
+ e.domEvent.stopPropagation();
217
+ },
218
+ });
219
+ }
220
+ } else if (record.isContractTagFolder) {
221
+ // 合同分类标签文件夹("按类型"排序时的虚拟分组):仅显示提示
222
+ menuItems.push({
223
+ key: 'contractTagFolderHint',
224
+ label: '合同分类文件夹',
225
+ icon: <InfoCircleOutlined />,
226
+ disabled: true,
227
+ });
228
+ } else if (record.fileType === 'folder' || record.isSecure) {
229
+ // 加密文件(非文件夹)允许下载
230
+ if (record.isSecure && record.fileType !== 'folder') {
231
+ // 普通文件显示所有操作
232
+ menuItems.push({
233
+ key: 'preview',
234
+ label: '预览',
235
+ icon: <InfoCircleOutlined />,
236
+ onClick: (e: any) => {
237
+ onPreview(record);
238
+ e.domEvent.stopPropagation();
239
+ },
240
+ });
241
+
242
+ // 在新窗口打开
243
+ if (onOpenInNewWindow) {
244
+ menuItems.push({
245
+ key: 'openInNewWindow',
246
+ label: '在新窗口打开',
247
+ icon: <ArrowUpOutlined />,
248
+ onClick: (e: any) => {
249
+ onOpenInNewWindow(record);
250
+ e.domEvent.stopPropagation();
251
+ },
252
+ });
253
+ }
254
+
255
+ // 可编辑文件类型添加编辑菜单项
256
+ if (onEdit && isEditableFileType(record.fileName)) {
257
+ menuItems.push({
258
+ key: 'edit',
259
+ label: '编辑',
260
+ icon: <EditOutlined />,
261
+ onClick: (e: any) => {
262
+ onEdit(record);
263
+ e.domEvent.stopPropagation();
264
+ },
265
+ });
266
+ }
267
+
268
+ // 查看历史版本
269
+ if (onViewVersionHistory && isEditableFileType(record.fileName)) {
270
+ menuItems.push({
271
+ key: 'versionHistory',
272
+ label: '查看历史版本',
273
+ icon: <HistoryOutlined />,
274
+ onClick: (e: any) => {
275
+ onViewVersionHistory(record);
276
+ e.domEvent.stopPropagation();
277
+ },
278
+ });
279
+ }
280
+ menuItems.push({
281
+ key: 'download',
282
+ label: '下载',
283
+ icon: <DownloadOutlined />,
284
+ onClick: (e: any) => {
285
+ onDownload(record);
286
+ e.domEvent.stopPropagation();
287
+ },
288
+ });
289
+ }
290
+
291
+ // 下载文件夹:仅管理员/负责人可用(onDownloadFolder 由上层按权限注入)
292
+ if (
293
+ record.fileType === 'folder' &&
294
+ onDownloadFolder &&
295
+ !record.isVirtualContractFolder &&
296
+ !record.isContractSubfolder &&
297
+ !record.isContractTagFolder
298
+ ) {
299
+ menuItems.push({
300
+ key: 'downloadFolder',
301
+ label: '下载文件夹',
302
+ icon: <DownloadOutlined />,
303
+ onClick: (e: any) => {
304
+ onDownloadFolder(record);
305
+ e.domEvent.stopPropagation();
306
+ },
307
+ });
308
+ }
309
+
310
+ menuItems.push(
311
+ {
312
+ key: 'rename',
313
+ label: '重命名',
314
+ icon: <EditOutlined />,
315
+ onClick: (e: any) => {
316
+ onRename(record);
317
+ e.domEvent.stopPropagation();
318
+ },
319
+ },
320
+ {
321
+ key: 'editDescs',
322
+ label: '编辑备注',
323
+ icon: <CommentOutlined />,
324
+ onClick: (e: any) => {
325
+ onEditDescs(record);
326
+ e.domEvent.stopPropagation();
327
+ },
328
+ }
329
+ );
330
+
331
+ // 文件夹仅支持复制到其他项目(递归复制整个文件夹);普通文件支持移动/复制
332
+ if (record.fileType === 'folder') {
333
+ if (onCopyToProject) {
334
+ menuItems.push({
335
+ key: 'copyToProject',
336
+ label: '复制到其他项目',
337
+ icon: <FolderAddOutlined />,
338
+ onClick: (e: any) => {
339
+ onCopyToProject(record);
340
+ e.domEvent.stopPropagation();
341
+ },
342
+ });
343
+ }
344
+ } else if (moveCopyToProjectMenu.length > 0) {
345
+ menuItems.push({
346
+ key: 'moveCopyToProject',
347
+ label: '移动/复制...',
348
+ icon: <ExportOutlined />,
349
+ children: moveCopyToProjectMenu,
350
+ });
351
+ }
352
+
353
+ // 管理员或负责人可以分享加密文件到微信
354
+ if (onShareToWechat && record.isSecure && isAdminOrOwner) {
355
+ menuItems.push({
356
+ key: 'shareToWechat',
357
+ label: '分享到微信',
358
+ icon: <ShareAltOutlined />,
359
+ onClick: (e: any) => {
360
+ onShareToWechat(record);
361
+ e.domEvent.stopPropagation();
362
+ },
363
+ });
364
+ }
365
+
366
+ // 加密文件(非文件夹)支持清理缓存
367
+ if (onClearCache && record.isSecure && record.fileType !== 'folder') {
368
+ menuItems.push({
369
+ key: 'clearCache',
370
+ label: '清理缓存',
371
+ icon: <ClearOutlined />,
372
+ onClick: (e: any) => {
373
+ onClearCache(record);
374
+ e.domEvent.stopPropagation();
375
+ },
376
+ });
377
+ }
378
+
379
+ if (record.fileType === 'folder' && onGenerateTempLink) {
380
+ menuItems.push({
381
+ key: 'generateTempLink',
382
+ label: '临时访问链接',
383
+ icon: <LinkOutlined />,
384
+ onClick: (e: any) => {
385
+ onGenerateTempLink(record);
386
+ e.domEvent.stopPropagation();
387
+ },
388
+ });
389
+ }
390
+
391
+ menuItems.push({
392
+ key: 'delete',
393
+ label: '删除',
394
+ icon: <DeleteOutlined />,
395
+ danger: true,
396
+ onClick: (e: any) => {
397
+ onDelete(record.id);
398
+ e.domEvent.stopPropagation();
399
+ },
400
+ });
401
+ } else {
402
+ // 普通文件显示所有操作
403
+ menuItems.push({
404
+ key: 'preview',
405
+ label: '预览',
406
+ icon: <InfoCircleOutlined />,
407
+ onClick: (e: any) => {
408
+ onPreview(record);
409
+ e.domEvent.stopPropagation();
410
+ },
411
+ });
412
+
413
+ // 在新窗口打开
414
+ if (onOpenInNewWindow) {
415
+ menuItems.push({
416
+ key: 'openInNewWindow',
417
+ label: '在新窗口打开',
418
+ icon: <ArrowUpOutlined />,
419
+ onClick: (e: any) => {
420
+ onOpenInNewWindow(record);
421
+ e.domEvent.stopPropagation();
422
+ },
423
+ });
424
+ }
425
+
426
+ // 可编辑文件类型添加编辑菜单项(PPT 编辑暂时关闭,仅保留预览)
427
+ if (onEdit && isEditableFileType(record.fileName) && !record.fileName.toLowerCase().endsWith('.pptx')) {
428
+ menuItems.push({
429
+ key: 'edit',
430
+ label: '编辑',
431
+ icon: <EditOutlined />,
432
+ onClick: (e: any) => {
433
+ onEdit(record);
434
+ e.domEvent.stopPropagation();
435
+ },
436
+ });
437
+ }
438
+
439
+ // 查看历史版本
440
+ if (onViewVersionHistory && isEditableFileType(record.fileName)) {
441
+ menuItems.push({
442
+ key: 'versionHistory',
443
+ label: '查看历史版本',
444
+ icon: <HistoryOutlined />,
445
+ onClick: (e: any) => {
446
+ onViewVersionHistory(record);
447
+ e.domEvent.stopPropagation();
448
+ },
449
+ });
450
+ }
451
+
452
+ menuItems.push(
453
+ {
454
+ key: 'download',
455
+ label: '下载',
456
+ icon: <DownloadOutlined />,
457
+ onClick: (e: any) => {
458
+ onDownload(record);
459
+ e.domEvent.stopPropagation();
460
+ },
461
+ },
462
+ {
463
+ key: 'rename',
464
+ label: '重命名',
465
+ icon: <EditOutlined />,
466
+ onClick: (e: any) => {
467
+ onRename(record);
468
+ e.domEvent.stopPropagation();
469
+ },
470
+ },
471
+ {
472
+ key: 'editDescs',
473
+ label: '编辑备注',
474
+ icon: <CommentOutlined />,
475
+ onClick: (e: any) => {
476
+ onEditDescs(record);
477
+ e.domEvent.stopPropagation();
478
+ },
479
+ }
480
+ );
481
+
482
+ // 添加移动/复制到其他项目子菜单(无任何回调时不显示)
483
+ if (moveCopyToProjectMenu.length > 0) {
484
+ menuItems.push({
485
+ key: 'moveCopyToProject',
486
+ label: '移动/复制...',
487
+ icon: <ExportOutlined />,
488
+ children: moveCopyToProjectMenu,
489
+ });
490
+ }
491
+
492
+ // 临时文件链接
493
+ if (onGenerateTempLink) {
494
+ menuItems.push({
495
+ key: 'generateTempLink',
496
+ label: '临时访问链接',
497
+ icon: <LinkOutlined />,
498
+ onClick: (e: any) => {
499
+ onGenerateTempLink(record);
500
+ e.domEvent.stopPropagation();
501
+ },
502
+ });
503
+ }
504
+
505
+ if (onCopyLink) {
506
+ menuItems.push({
507
+ key: 'copyLink',
508
+ label: '复制链接',
509
+ icon: <CopyOutlined />,
510
+ onClick: (e: any) => {
511
+ onCopyLink(record);
512
+ e.domEvent.stopPropagation();
513
+ },
514
+ });
515
+ }
516
+
517
+ // 分享到微信(有下载权限时显示)
518
+ if (onShareToWechat) {
519
+ menuItems.push({
520
+ key: 'shareToWechat',
521
+ label: '分享到微信',
522
+ icon: <ShareAltOutlined />,
523
+ onClick: (e: any) => {
524
+ onShareToWechat(record);
525
+ e.domEvent.stopPropagation();
526
+ },
527
+ });
528
+ }
529
+
530
+ if (onClearCache && record.fileType !== 'folder') {
531
+ menuItems.push({
532
+ key: 'clearCache',
533
+ label: '清理缓存',
534
+ icon: <ClearOutlined />,
535
+ onClick: (e: any) => {
536
+ onClearCache(record);
537
+ e.domEvent.stopPropagation();
538
+ },
539
+ });
540
+ }
541
+
542
+ menuItems.push({
543
+ key: 'delete',
544
+ label: '删除',
545
+ icon: <DeleteOutlined />,
546
+ danger: true,
547
+ onClick: (e: any) => {
548
+ onDelete(record.id);
549
+ e.domEvent.stopPropagation();
550
+ },
551
+ });
552
+ }
553
+
554
+ const isContextMenuMode = open !== undefined;
555
+
556
+ const menuRef = useRef<HTMLDivElement>(null);
557
+ const [menuPosition, setMenuPosition] = useState({ left: 0, top: 0 });
558
+
559
+ useLayoutEffect(() => {
560
+ if (!isContextMenuMode || !open || !position || !menuRef.current) {
561
+ return;
562
+ }
563
+
564
+ const viewportPadding = 8;
565
+ const menuWidth = menuRef.current.offsetWidth || 160;
566
+ const menuHeight = menuRef.current.offsetHeight || 192;
567
+ const maxLeft = window.innerWidth - menuWidth - viewportPadding;
568
+ const maxTop = window.innerHeight - menuHeight - viewportPadding;
569
+
570
+ setMenuPosition({
571
+ left: Math.max(viewportPadding, Math.min(position.x, maxLeft)),
572
+ top: Math.max(viewportPadding, Math.min(position.y, maxTop)),
573
+ });
574
+ }, [isContextMenuMode, open, position]);
575
+
576
+ if (isContextMenuMode && open) {
577
+ return (
578
+ <div
579
+ className="fixed z-50"
580
+ style={{
581
+ left: menuPosition.left || position?.x || 0,
582
+ top: menuPosition.top || position?.y || 0,
583
+ minWidth: 160,
584
+ }}
585
+ onClick={(e) => {
586
+ e.stopPropagation();
587
+ }}
588
+ >
589
+ <div ref={menuRef}>
590
+ <Menu
591
+ items={menuItems}
592
+ mode="vertical"
593
+ style={{
594
+ borderRadius: 6,
595
+ boxShadow: '0 2px 8px rgb(var(--shadow-color) / 0.15)',
596
+ background: 'rgb(var(--color-surface))',
597
+ border: '1px solid rgb(var(--color-border))',
598
+ padding: '4px 0',
599
+ fontSize: '14px',
600
+ minWidth: 160,
601
+ maxHeight: 'calc(100vh - 16px)',
602
+ overflowY: 'auto',
603
+ }}
604
+ />
605
+ </div>
606
+ </div>
607
+ );
608
+ }
609
+
610
+ return (
611
+ <div className={className}>
612
+ {/* {isMobile && (
613
+ <Dropdown
614
+ menu={{ items: menuItems }}
615
+ trigger={[trigger]}
616
+ placement={placement}
617
+ >
618
+ {trigger === 'click' ? (
619
+ <Button type="text" icon={<MoreOutlined />} onClick={(e) => e.stopPropagation()} />
620
+ ) : (
621
+ <div />
622
+ )}
623
+ </Dropdown>)} */}
624
+ </div>
625
+ );
626
+ };
627
+
628
+ export const FileContextMenu = memo(FileContextMenuComponent);