@mxmweb/zui 1.1.10 → 1.1.13

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 (127) hide show
  1. package/.editorconfig +38 -0
  2. package/.prettierignore +16 -0
  3. package/.prettierrc +17 -0
  4. package/.releaserc.json +36 -0
  5. package/CHANGELOG.md +58 -0
  6. package/CONTRIBUTING.md +111 -0
  7. package/NPMREADME.md +0 -0
  8. package/README.md +4 -0
  9. package/bash.exe.stackdump +40 -0
  10. package/components.json +21 -0
  11. package/dist/README.md +4 -0
  12. package/dist/package.json +30 -0
  13. package/eslint.config.js +92 -0
  14. package/index.html +13 -0
  15. package/package.json +39 -15
  16. package/postcss.config.cjs +19 -0
  17. package/public/mock.csv +16 -0
  18. package/public/mock_/345/211/257/346/234/254.csv +16 -0
  19. package/public/vite.svg +1 -0
  20. package/src/Preview.tsx +15 -0
  21. package/src/assets/img/excel.png +0 -0
  22. package/src/assets/img/img.png +0 -0
  23. package/src/assets/img/pdf.png +0 -0
  24. package/src/assets/img/ppt.png +0 -0
  25. package/src/assets/img/txt.png +0 -0
  26. package/src/assets/img/word.png +0 -0
  27. package/src/containers/DashboardContainer.tsx +507 -0
  28. package/src/containers/DockContainer.tsx +186 -0
  29. package/src/containers/style.css +37 -0
  30. package/src/elements/Button.tsx +118 -0
  31. package/src/elements/CustomDock.tsx +287 -0
  32. package/src/elements/DropDownButton.tsx +249 -0
  33. package/src/elements/DropdownMenu.tsx +184 -0
  34. package/src/elements/GoggleNavbar.tsx +184 -0
  35. package/src/elements/Uploader/README.md +249 -0
  36. package/src/elements/Uploader/UploadItem.tsx +298 -0
  37. package/src/elements/Uploader/example.tsx +95 -0
  38. package/src/elements/Uploader/index.tsx +702 -0
  39. package/src/elements/Uploader/styles.tsx +291 -0
  40. package/src/elements/Uploader/types.ts +119 -0
  41. package/src/elements/Uploader/utils.ts +200 -0
  42. package/src/elements/Uploader.tsx +3 -0
  43. package/src/examples/DockContainerExample.tsx +237 -0
  44. package/src/icons/Icon.tsx +82 -0
  45. package/src/icons/index.tsx +92 -0
  46. package/src/icons/lazyIndex.tsx +49 -0
  47. package/src/icons/rag/csv.svg +3 -0
  48. package/src/icons/rag/document.svg +3 -0
  49. package/src/icons/rag/excel.svg +3 -0
  50. package/src/icons/rag/file.svg +3 -0
  51. package/src/icons/rag/folder.svg +5 -0
  52. package/src/icons/rag/json.svg +3 -0
  53. package/src/icons/rag/knowledgebase.svg +3 -0
  54. package/src/icons/rag/netretrive.svg +3 -0
  55. package/src/icons/rag/odf.svg +7 -0
  56. package/src/icons/rag/pdf.svg +3 -0
  57. package/src/icons/rag/pic.svg +3 -0
  58. package/src/icons/rag/ppt.svg +3 -0
  59. package/src/icons/rag/think.svg +6 -0
  60. package/src/icons/rag/txt.svg +3 -0
  61. package/src/icons/rag/url.svg +3 -0
  62. package/src/icons/rag/word.svg +3 -0
  63. package/src/icons/rag/wps.svg +3 -0
  64. package/src/icons/rag/zip.svg +7 -0
  65. package/src/lib_enter.ts +27 -0
  66. package/src/main.tsx +11 -0
  67. package/src/style.css +9 -0
  68. package/src/theme/styledTheme.tsx +253 -0
  69. package/src/type.d.ts +0 -0
  70. package/src/types/images.d.ts +12 -0
  71. package/src/types/svg-modules.d.ts +24 -0
  72. package/src/vite-env.d.ts +11 -0
  73. package/tailwind.config.js +170 -0
  74. package/tsconfig.app.json +29 -0
  75. package/tsconfig.app.tsbuildinfo +11 -0
  76. package/tsconfig.json +13 -0
  77. package/tsconfig.node.json +22 -0
  78. package/tsconfig.node.tsbuildinfo +1 -0
  79. package/vite.config.ts +165 -0
  80. /package/{Preview.d.ts → dist/Preview.d.ts} +0 -0
  81. /package/{assets → dist/assets}/style.css +0 -0
  82. /package/{containers → dist/containers}/DashboardContainer.d.ts +0 -0
  83. /package/{containers → dist/containers}/DockContainer.d.ts +0 -0
  84. /package/{elements → dist/elements}/Button.d.ts +0 -0
  85. /package/{elements → dist/elements}/CustomDock.d.ts +0 -0
  86. /package/{elements → dist/elements}/DropDownButton.d.ts +0 -0
  87. /package/{elements → dist/elements}/DropdownMenu.d.ts +0 -0
  88. /package/{elements → dist/elements}/GoggleNavbar.d.ts +0 -0
  89. /package/{elements → dist/elements}/Uploader/UploadItem.d.ts +0 -0
  90. /package/{elements → dist/elements}/Uploader/example.d.ts +0 -0
  91. /package/{elements → dist/elements}/Uploader/index.d.ts +0 -0
  92. /package/{elements → dist/elements}/Uploader/styles.d.ts +0 -0
  93. /package/{elements → dist/elements}/Uploader/types.d.ts +0 -0
  94. /package/{elements → dist/elements}/Uploader/utils.d.ts +0 -0
  95. /package/{elements → dist/elements}/Uploader.d.ts +0 -0
  96. /package/{examples → dist/examples}/DockContainerExample.d.ts +0 -0
  97. /package/{icons → dist/icons}/Icon.d.ts +0 -0
  98. /package/{icons → dist/icons}/Icon.tsx +0 -0
  99. /package/{icons → dist/icons}/index.d.ts +0 -0
  100. /package/{icons → dist/icons}/index.tsx +0 -0
  101. /package/{icons → dist/icons}/lazyIndex.d.ts +0 -0
  102. /package/{icons → dist/icons}/lazyIndex.tsx +0 -0
  103. /package/{icons → dist/icons}/rag/csv.svg +0 -0
  104. /package/{icons → dist/icons}/rag/document.svg +0 -0
  105. /package/{icons → dist/icons}/rag/excel.svg +0 -0
  106. /package/{icons → dist/icons}/rag/file.svg +0 -0
  107. /package/{icons → dist/icons}/rag/folder.svg +0 -0
  108. /package/{icons → dist/icons}/rag/json.svg +0 -0
  109. /package/{icons → dist/icons}/rag/knowledgebase.svg +0 -0
  110. /package/{icons → dist/icons}/rag/netretrive.svg +0 -0
  111. /package/{icons → dist/icons}/rag/odf.svg +0 -0
  112. /package/{icons → dist/icons}/rag/pdf.svg +0 -0
  113. /package/{icons → dist/icons}/rag/pic.svg +0 -0
  114. /package/{icons → dist/icons}/rag/ppt.svg +0 -0
  115. /package/{icons → dist/icons}/rag/think.svg +0 -0
  116. /package/{icons → dist/icons}/rag/txt.svg +0 -0
  117. /package/{icons → dist/icons}/rag/url.svg +0 -0
  118. /package/{icons → dist/icons}/rag/word.svg +0 -0
  119. /package/{icons → dist/icons}/rag/wps.svg +0 -0
  120. /package/{icons → dist/icons}/rag/zip.svg +0 -0
  121. /package/{index.js → dist/index.js} +0 -0
  122. /package/{lib_enter.d.ts → dist/lib_enter.d.ts} +0 -0
  123. /package/{main.d.ts → dist/main.d.ts} +0 -0
  124. /package/{mock.csv → dist/mock.csv} +0 -0
  125. /package/{mock_ → dist/mock_}/345/211/257/346/234/254.csv" +0 -0
  126. /package/{theme → dist/theme}/styledTheme.d.ts +0 -0
  127. /package/{vite.svg → dist/vite.svg} +0 -0
@@ -0,0 +1,507 @@
1
+ import React from "react";
2
+ import { Outlet } from "react-router-dom";
3
+
4
+ import { ArrowLeft, ChevronRight } from "lucide-react";
5
+ import styled from "styled-components";
6
+ import { defaultTheme, type AppTheme } from "../theme/styledTheme";
7
+ import CustomDock, { type DockItem, type ActiveMode } from "../elements/CustomDock";
8
+
9
+ // 面包屑项类型定义
10
+ export interface BreadcrumbItem {
11
+ id: string;
12
+ label: string;
13
+ path?: string;
14
+ onClick?: () => void;
15
+ }
16
+
17
+ // 事件发射器类型定义
18
+ export interface EventsEmit {
19
+ onBreadcrumbClick?: (item: BreadcrumbItem, index: number) => void;
20
+ onGoBack?: () => void;
21
+ onTitleClick?: () => void;
22
+ onDockItemClick?: (item: DockItem, index: number) => void;
23
+ onDockActiveChange?: (activeId: string | null, item: DockItem | null) => void; // 单选模式
24
+ onDockActiveChangeMultiple?: (activeIds: string[], items: DockItem[]) => void; // 多选模式
25
+ [key: string]: any;
26
+ }
27
+
28
+ // Styled Components
29
+ const LayoutContainer = styled.div`
30
+ display: flex;
31
+ justify-content: flex-start;
32
+ align-items: flex-start;
33
+ margin-left: 20px;
34
+ flex-direction: column;
35
+ height: 100%;
36
+ width: 100%;
37
+ max-width: 98%;
38
+ position: relative;
39
+ `;
40
+
41
+ const HeaderSection = styled.div`
42
+ display: flex;
43
+ height: 38px;
44
+ z-index: 10;
45
+ background: transparent;
46
+ gap: 8px;
47
+ flex-direction: row;
48
+ align-items: center;
49
+ justify-content: flex-start;
50
+ width: 100%;
51
+ min-width: 0; /* 确保 flex 子元素可以收缩 */
52
+ `;
53
+
54
+ const BackButton = styled.button`
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 32px;
59
+ height: 32px;
60
+ background: transparent;
61
+ border: none;
62
+ cursor: pointer;
63
+
64
+ /* 仅让图标在 hover 时上浮,按钮背景保持透明 */
65
+ svg {
66
+ transition: transform 0.2s ease;
67
+ }
68
+
69
+ &:hover {
70
+ background: transparent;
71
+ }
72
+
73
+ &:hover svg {
74
+ transform: translateY(-1px);
75
+ }
76
+
77
+ &:active svg {
78
+ transform: translateY(0);
79
+ }
80
+ `;
81
+
82
+ const TitleText = styled.div`
83
+ line-height: 1.4;
84
+ cursor: pointer;
85
+ transition: color 0.2s ease;
86
+ white-space: nowrap;
87
+ overflow: hidden;
88
+ text-overflow: ellipsis;
89
+ max-width: 200px;
90
+ min-width: 0;
91
+
92
+ &:hover {
93
+ color: #007bff;
94
+ }
95
+ `;
96
+
97
+ const DescriptionText = styled.div`
98
+ line-height: 1.4;
99
+ white-space: nowrap;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ flex: 1;
103
+ min-width: 0;
104
+ `;
105
+
106
+ // 面包屑容器样式
107
+ const BreadcrumbContainer = styled.div`
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 4px;
111
+ flex-wrap: nowrap;
112
+ max-width: 50%;
113
+ min-width: 0;
114
+ overflow: hidden;
115
+ `;
116
+
117
+ // 面包屑项样式
118
+ const BreadcrumbItem = styled.div<{ $isLast?: boolean; $isClickable?: boolean }>`
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 4px;
122
+ font-size: 14px;
123
+ color: ${props => props.$isLast ? '#343a40' : '#6c757d'};
124
+ font-weight: ${props => props.$isLast ? '600' : 'normal'};
125
+ cursor: ${props => props.$isClickable ? 'pointer' : 'default'};
126
+ transition: color 0.2s ease;
127
+ white-space: nowrap;
128
+ overflow: hidden;
129
+ text-overflow: ellipsis;
130
+ max-width: 120px;
131
+
132
+ &:hover {
133
+ color: ${props => props.$isLast ? '#007bff' : 'inherit'};
134
+ }
135
+ `;
136
+
137
+ // 面包屑分隔符样式
138
+ const BreadcrumbSeparator = styled.div`
139
+ display: flex;
140
+ align-items: center;
141
+ color: #6c757d;
142
+ margin: 0 2px;
143
+ `;
144
+
145
+ const ContentSection = styled.div`
146
+ display: flex;
147
+ flex-direction: column;
148
+ width: 100%;
149
+ height: calc(100% - 45px);
150
+ min-height: 650px;
151
+ overflow: auto;
152
+ `;
153
+
154
+ // Dock 区域样式
155
+ const DockSection = styled.div`
156
+ position: fixed;
157
+ bottom: 20px;
158
+ left: 50%;
159
+ transform: translateX(-50%);
160
+ z-index: 1000;
161
+ `;
162
+
163
+ interface DashboardContainerProps {
164
+ children?: React.ReactNode;
165
+ goBack?: () => void;
166
+ title?: string;
167
+ description?: string;
168
+ breadcrumbs?: BreadcrumbItem[];
169
+ dockItems?: DockItem[];
170
+ dockActiveMode?: ActiveMode; // Dock 激活模式
171
+ defaultDockActiveId?: string; // 单选模式:默认激活的 Dock 项ID
172
+ defaultDockActiveIds?: string[]; // 多选模式:默认激活的 Dock 项ID数组
173
+ styles?: {
174
+ theme?: AppTheme;
175
+ };
176
+ eventsEmit?: EventsEmit;
177
+ }
178
+
179
+ const DashboardContainer: React.FC<DashboardContainerProps> = ({
180
+ children,
181
+ goBack,
182
+ title,
183
+ description,
184
+ breadcrumbs,
185
+ dockItems,
186
+ dockActiveMode = 'single',
187
+ defaultDockActiveId,
188
+ defaultDockActiveIds,
189
+ styles,
190
+ eventsEmit,
191
+ }) => {
192
+ // 主题优先级:props.theme > props.styles?.theme > context
193
+ const theme = styles?.theme || defaultTheme;
194
+
195
+ // 使用 useRef 来获取父容器(HeaderSection)的宽度
196
+ const headerRef = React.useRef<HTMLDivElement>(null);
197
+
198
+ // 处理返回按钮点击事件
199
+ const handleGoBack = () => {
200
+ if (eventsEmit?.onGoBack) {
201
+ eventsEmit.onGoBack();
202
+ } else if (goBack) {
203
+ goBack();
204
+ }
205
+ };
206
+
207
+ // 处理标题点击事件
208
+ const handleTitleClick = () => {
209
+ if (eventsEmit?.onTitleClick) {
210
+ eventsEmit.onTitleClick();
211
+ }
212
+ };
213
+
214
+ // 处理面包屑点击事件
215
+ const handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {
216
+ if (eventsEmit?.onBreadcrumbClick) {
217
+ eventsEmit.onBreadcrumbClick(item, index);
218
+ } else if (item.onClick) {
219
+ item.onClick();
220
+ }
221
+ };
222
+
223
+ // 处理 Dock 项点击事件
224
+ const handleDockItemClick = (item: DockItem, index: number) => {
225
+ if (eventsEmit?.onDockItemClick) {
226
+ eventsEmit.onDockItemClick(item, index);
227
+ } else if (item.onClick) {
228
+ item.onClick();
229
+ }
230
+ };
231
+
232
+ // 处理 Dock 激活状态变化事件(单选模式)
233
+ const handleDockActiveChange = (activeId: string | null, item: DockItem | null) => {
234
+ if (eventsEmit?.onDockActiveChange) {
235
+ eventsEmit.onDockActiveChange(activeId, item);
236
+ }
237
+ };
238
+
239
+ // 处理 Dock 激活状态变化事件(多选模式)
240
+ const handleDockActiveChangeMultiple = (activeIds: string[], items: DockItem[]) => {
241
+ if (eventsEmit?.onDockActiveChangeMultiple) {
242
+ eventsEmit.onDockActiveChangeMultiple(activeIds, items);
243
+ }
244
+ };
245
+
246
+ // 智能渲染面包屑,根据父元素宽度动态计算显示项
247
+ const renderBreadcrumbs = () => {
248
+ if (!breadcrumbs || breadcrumbs.length === 0) return null;
249
+
250
+ // 使用 useRef 来获取父容器(HeaderSection)的宽度
251
+ const headerRef = React.useRef<HTMLDivElement>(null);
252
+ const [visibleBreadcrumbs, setVisibleBreadcrumbs] = React.useState<{
253
+ start: number;
254
+ end: number;
255
+ showEllipsis: boolean;
256
+ }>({ start: 0, end: breadcrumbs.length, showEllipsis: false });
257
+
258
+ // 计算哪些面包屑项应该显示
259
+ const calculateVisibleBreadcrumbs = React.useCallback(() => {
260
+ if (!headerRef.current || breadcrumbs.length <= 3) {
261
+ setVisibleBreadcrumbs({ start: 0, end: breadcrumbs.length, showEllipsis: false });
262
+ return;
263
+ }
264
+
265
+ const parentWidth = headerRef.current.offsetWidth;
266
+ const maxWidth = parentWidth * 0.5; // 面包屑最大宽度为父容器的50%
267
+
268
+ // 估算每个面包屑项的平均宽度(包括分隔符)
269
+ const estimatedItemWidth = 120; // 面包屑项最大宽度
270
+ const separatorWidth = 20; // 分隔符宽度
271
+ const ellipsisWidth = 30; // 省略号宽度
272
+
273
+ // 计算可以显示多少个面包屑项
274
+ let maxVisibleItems = Math.floor((maxWidth - ellipsisWidth) / (estimatedItemWidth + separatorWidth));
275
+
276
+ // 至少显示第一个和最后一个
277
+ if (maxVisibleItems < 2) {
278
+ maxVisibleItems = 2;
279
+ }
280
+
281
+ // 如果所有项都能显示,就不需要省略号
282
+ if (maxVisibleItems >= breadcrumbs.length) {
283
+ setVisibleBreadcrumbs({ start: 0, end: breadcrumbs.length, showEllipsis: false });
284
+ return;
285
+ }
286
+
287
+ // 计算显示策略:优先显示后面的项(因为通常后面的更重要)
288
+ const startIndex = 0; // 总是显示第一个
289
+ const endIndex = Math.min(maxVisibleItems - 1, breadcrumbs.length); // 显示到倒数第几个
290
+
291
+ setVisibleBreadcrumbs({
292
+ start: startIndex,
293
+ end: endIndex,
294
+ showEllipsis: endIndex < breadcrumbs.length - 1
295
+ });
296
+ }, [breadcrumbs.length]);
297
+
298
+ // 监听父容器大小变化
299
+ React.useEffect(() => {
300
+ calculateVisibleBreadcrumbs();
301
+
302
+ const resizeObserver = new ResizeObserver(() => {
303
+ calculateVisibleBreadcrumbs();
304
+ });
305
+
306
+ if (headerRef.current) {
307
+ resizeObserver.observe(headerRef.current);
308
+ }
309
+
310
+ return () => {
311
+ resizeObserver.disconnect();
312
+ };
313
+ }, [calculateVisibleBreadcrumbs]);
314
+
315
+ // 如果面包屑数量少于等于3个,直接显示
316
+ if (breadcrumbs.length <= 3) {
317
+ return (
318
+ <BreadcrumbContainer>
319
+ {breadcrumbs.map((item, index) => (
320
+ <React.Fragment key={item.id}>
321
+ <BreadcrumbItem
322
+ $isLast={index === breadcrumbs.length - 1}
323
+ $isClickable={Boolean(index < breadcrumbs.length - 1 || item.onClick)}
324
+ onClick={() => handleBreadcrumbClick(item, index)}
325
+ style={{
326
+ color: index === breadcrumbs.length - 1
327
+ ? theme.colors.text
328
+ : theme.colors.textSecondary || theme.colors.disabledText,
329
+ }}
330
+ title={item.label}
331
+ >
332
+ {item.label}
333
+ </BreadcrumbItem>
334
+ {index < breadcrumbs.length - 1 && (
335
+ <BreadcrumbSeparator>
336
+ <ChevronRight size={14} />
337
+ </BreadcrumbSeparator>
338
+ )}
339
+ </React.Fragment>
340
+ ))}
341
+ </BreadcrumbContainer>
342
+ );
343
+ }
344
+
345
+ // 动态计算显示的面包屑项
346
+ const { start, end, showEllipsis } = visibleBreadcrumbs;
347
+
348
+ return (
349
+ <BreadcrumbContainer>
350
+ {/* 显示的面包屑项 */}
351
+ {breadcrumbs.slice(start, end).map((item, index) => (
352
+ <React.Fragment key={item.id}>
353
+ <BreadcrumbItem
354
+ $isLast={index === end - start - 1 && !showEllipsis}
355
+ $isClickable={Boolean(index < end - start - 1 || item.onClick)}
356
+ onClick={() => handleBreadcrumbClick(item, start + index)}
357
+ style={{
358
+ color: index === end - start - 1 && !showEllipsis
359
+ ? theme.colors.text
360
+ : theme.colors.textSecondary || theme.colors.disabledText,
361
+ }}
362
+ title={item.label}
363
+ >
364
+ {item.label}
365
+ </BreadcrumbItem>
366
+ {index < end - start - 1 && (
367
+ <BreadcrumbSeparator>
368
+ <ChevronRight size={14} />
369
+ </BreadcrumbSeparator>
370
+ )}
371
+ </React.Fragment>
372
+ ))}
373
+
374
+ {/* 省略号 */}
375
+ {showEllipsis && (
376
+ <>
377
+ <BreadcrumbSeparator>
378
+ <ChevronRight size={14} />
379
+ </BreadcrumbSeparator>
380
+ <BreadcrumbItem
381
+ $isLast={false}
382
+ $isClickable={false}
383
+ style={{
384
+ color: theme.colors.textSecondary || theme.colors.disabledText,
385
+ cursor: 'default',
386
+ maxWidth: 'auto',
387
+ }}
388
+ title={`${breadcrumbs.slice(end, -1).map(item => item.label).join(' > ')}`}
389
+ >
390
+ ...
391
+ </BreadcrumbItem>
392
+ <BreadcrumbSeparator>
393
+ <ChevronRight size={14} />
394
+ </BreadcrumbSeparator>
395
+ {/* 最后一个面包屑 */}
396
+ <BreadcrumbItem
397
+ $isLast={true}
398
+ $isClickable={Boolean(breadcrumbs[breadcrumbs.length - 1].onClick)}
399
+ onClick={() => handleBreadcrumbClick(breadcrumbs[breadcrumbs.length - 1], breadcrumbs.length - 1)}
400
+ style={{
401
+ color: theme.colors.text,
402
+ }}
403
+ title={breadcrumbs[breadcrumbs.length - 1].label}
404
+ >
405
+ {breadcrumbs[breadcrumbs.length - 1].label}
406
+ </BreadcrumbItem>
407
+ </>
408
+ )}
409
+ </BreadcrumbContainer>
410
+ );
411
+ };
412
+
413
+ return (
414
+ <LayoutContainer
415
+ style={{
416
+ background: theme.colors.appBackground,
417
+ color: theme.colors.text,
418
+ fontFamily: theme.fonts?.body?.family || 'PingFang SC, Microsoft YaHei, Arial, sans-serif',
419
+ }}
420
+ >
421
+ {/* 头部标题区域 */}
422
+ {(title || description || goBack || breadcrumbs) && (
423
+ <HeaderSection ref={headerRef}>
424
+ {/* 返回按钮 */}
425
+ {goBack && (
426
+ <BackButton
427
+ onClick={handleGoBack}
428
+ style={{
429
+ color: theme.colors.primary,
430
+ borderRadius: theme.space.radius || '6px',
431
+ }}
432
+ title="返回"
433
+ >
434
+ <ArrowLeft size={18} />
435
+ </BackButton>
436
+ )}
437
+
438
+ {/* 面包屑导航 */}
439
+ {renderBreadcrumbs()}
440
+
441
+ {/* 标题 */}
442
+ {title && (
443
+ <TitleText
444
+ onClick={handleTitleClick}
445
+ style={{
446
+ color: theme.colors.text,
447
+ fontSize: theme.fonts?.heading?.size || '16px',
448
+ fontWeight: theme.fonts?.heading?.weight || '600',
449
+ }}
450
+ title={title} // 添加 hover 提示
451
+ >
452
+ {title}
453
+ </TitleText>
454
+ )}
455
+
456
+ {/* 描述 */}
457
+ {description && (
458
+ <DescriptionText
459
+ style={{
460
+ color: theme.colors.textSecondary || theme.colors.disabledText,
461
+ opacity: 0.7,
462
+ fontSize: theme.fonts?.body?.size || '12px',
463
+ }}
464
+ title={description} // 添加 hover 提示
465
+ >
466
+ {description}
467
+ </DescriptionText>
468
+ )}
469
+ </HeaderSection>
470
+ )}
471
+
472
+ {/* 内容区域 */}
473
+ <ContentSection
474
+ style={{
475
+ background: theme.colors.dashboardBackground || 'transparent',
476
+ border: `1px solid ${theme.colors.border}` || undefined,
477
+ borderRadius: theme.space.radius || undefined,
478
+ boxShadow: theme.colors.shadow || undefined,
479
+
480
+ }}
481
+ >
482
+ {children || <Outlet />}
483
+ </ContentSection>
484
+
485
+ {/* Dock 导航区域 */}
486
+ {dockItems && dockItems.length > 0 && (
487
+ <DockSection>
488
+ <CustomDock
489
+ items={dockItems}
490
+ itemWidth={48}
491
+ itemHeight={48}
492
+ magnification={1.5}
493
+ itemGap={8}
494
+ activeMode={dockActiveMode}
495
+ defaultActiveId={defaultDockActiveId}
496
+ defaultActiveIds={defaultDockActiveIds}
497
+ onItemClick={handleDockItemClick}
498
+ onActiveChange={handleDockActiveChange}
499
+ onActiveChangeMultiple={handleDockActiveChangeMultiple}
500
+ />
501
+ </DockSection>
502
+ )}
503
+ </LayoutContainer>
504
+ );
505
+ };
506
+
507
+ export default DashboardContainer;
@@ -0,0 +1,186 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { defaultTheme, type AppTheme } from '../theme/styledTheme';
4
+ import CustomDock, { type DockItem, type ActiveMode } from '../elements/CustomDock';
5
+
6
+ // 全屏容器属性
7
+ export interface FullScreenContainerProps {
8
+ children?: React.ReactNode;
9
+ backgroundImage?: string; // 背景图片URL
10
+ backgroundColor?: string; // 背景颜色(当没有背景图时使用)
11
+ header?: React.ReactNode; // 自定义头部组件
12
+ dockItems?: DockItem[]; // Dock 导航项
13
+ dockActiveMode?: ActiveMode; // Dock 激活模式
14
+ defaultDockActiveId?: string; // 单选模式:默认激活的 Dock 项ID
15
+ defaultDockActiveIds?: string[]; // 多选模式:默认激活的 Dock 项ID数组
16
+ styles?: {
17
+ theme?: AppTheme;
18
+ };
19
+ eventsEmit?: {
20
+ onDockItemClick?: (item: DockItem, index: number) => void;
21
+ onDockActiveChange?: (activeId: string | null, item: DockItem | null) => void;
22
+ onDockActiveChangeMultiple?: (activeIds: string[], items: DockItem[]) => void;
23
+ [key: string]: any;
24
+ };
25
+ }
26
+
27
+ // 全屏背景容器
28
+ const FullScreenWrapper = styled.div.withConfig({
29
+ shouldForwardProp: (prop) => !['backgroundImage', 'backgroundColor'].includes(prop),
30
+ })<{
31
+ backgroundImage?: string;
32
+ backgroundColor?: string;
33
+ }>`
34
+ position: fixed;
35
+ top: 0;
36
+ left: 0;
37
+ width: 100vw;
38
+ height: 100vh;
39
+ background: ${props =>
40
+ props.backgroundImage
41
+ ? `url(${props.backgroundImage}) center/cover no-repeat`
42
+ : props.backgroundColor || '#1a1a1a'
43
+ };
44
+ display: flex;
45
+ flex-direction: column;
46
+ overflow: hidden;
47
+ z-index: 1;
48
+ `;
49
+
50
+ // 头部区域
51
+ const HeaderSection = styled.div`
52
+ height: 48px;
53
+ width: 100%;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ background: transparent;
58
+ z-index: 10;
59
+ flex-shrink: 0;
60
+ `;
61
+
62
+ // 中间滚动区域
63
+ const ScrollableContent = styled.div`
64
+ flex: 1;
65
+ width: 100%;
66
+ overflow-y: auto;
67
+ overflow-x: hidden;
68
+ position: relative;
69
+ z-index: 2;
70
+ background: transparent;
71
+
72
+ /* 自定义滚动条样式 */
73
+ &::-webkit-scrollbar {
74
+ width: 6px;
75
+ }
76
+
77
+ &::-webkit-scrollbar-track {
78
+ background: rgba(255, 255, 255, 0.1);
79
+ border-radius: 3px;
80
+ }
81
+
82
+ &::-webkit-scrollbar-thumb {
83
+ background: rgba(255, 255, 255, 0.3);
84
+ border-radius: 3px;
85
+ }
86
+
87
+ &::-webkit-scrollbar-thumb:hover {
88
+ background: rgba(255, 255, 255, 0.5);
89
+ }
90
+ `;
91
+
92
+ // Dock 区域
93
+ const DockSection = styled.div`
94
+ height: 80px;
95
+ width: 100%;
96
+ display: flex;
97
+ align-items: flex-end;
98
+ justify-content: center;
99
+ padding: 12px 0;
100
+ background: transparent;
101
+ z-index: 10;
102
+ flex-shrink: 0;
103
+ `;
104
+
105
+ const DockContainer: React.FC<FullScreenContainerProps> = ({
106
+ children,
107
+ backgroundImage,
108
+ backgroundColor,
109
+ header,
110
+ dockItems,
111
+ dockActiveMode = 'single',
112
+ defaultDockActiveId,
113
+ defaultDockActiveIds,
114
+ styles,
115
+ eventsEmit,
116
+ }) => {
117
+ const theme = styles?.theme || defaultTheme;
118
+
119
+ // 处理 Dock 项点击事件
120
+ const handleDockItemClick = (item: DockItem, index: number) => {
121
+ if (eventsEmit?.onDockItemClick) {
122
+ eventsEmit.onDockItemClick(item, index);
123
+ } else if (item.onClick) {
124
+ item.onClick();
125
+ }
126
+ };
127
+
128
+ // 处理 Dock 激活状态变化事件(单选模式)
129
+ const handleDockActiveChange = (activeId: string | null, item: DockItem | null) => {
130
+ if (eventsEmit?.onDockActiveChange) {
131
+ eventsEmit.onDockActiveChange(activeId, item);
132
+ }
133
+ };
134
+
135
+ // 处理 Dock 激活状态变化事件(多选模式)
136
+ const handleDockActiveChangeMultiple = (activeIds: string[], items: DockItem[]) => {
137
+ if (eventsEmit?.onDockActiveChangeMultiple) {
138
+ eventsEmit.onDockActiveChangeMultiple(activeIds, items);
139
+ }
140
+ };
141
+
142
+ return (
143
+ <FullScreenWrapper
144
+ backgroundImage={backgroundImage}
145
+ backgroundColor={backgroundColor}
146
+ >
147
+ {/* 头部区域 */}
148
+ {header && (
149
+ <HeaderSection>
150
+ {header}
151
+ </HeaderSection>
152
+ )}
153
+
154
+ {/* 中间滚动内容区域 */}
155
+ <ScrollableContent
156
+ style={{
157
+ color: theme.colors.text,
158
+ fontFamily: theme.fonts?.body?.family || 'PingFang SC, Microsoft YaHei, Arial, sans-serif',
159
+ }}
160
+ >
161
+ {children}
162
+ </ScrollableContent>
163
+
164
+ {/* 底部 Dock 区域 */}
165
+ {dockItems && dockItems.length > 0 && (
166
+ <DockSection>
167
+ <CustomDock
168
+ items={dockItems}
169
+ itemWidth={48}
170
+ itemHeight={48}
171
+ magnification={1.5}
172
+ itemGap={8}
173
+ activeMode={dockActiveMode}
174
+ defaultActiveId={defaultDockActiveId}
175
+ defaultActiveIds={defaultDockActiveIds}
176
+ onItemClick={handleDockItemClick}
177
+ onActiveChange={handleDockActiveChange}
178
+ onActiveChangeMultiple={handleDockActiveChangeMultiple}
179
+ />
180
+ </DockSection>
181
+ )}
182
+ </FullScreenWrapper>
183
+ );
184
+ };
185
+
186
+ export default DockContainer;