@mxmweb/zui 1.1.10 → 1.1.14
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/.editorconfig +38 -0
- package/.prettierignore +16 -0
- package/.prettierrc +17 -0
- package/.releaserc.json +36 -0
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +111 -0
- package/NPMREADME.md +0 -0
- package/README.md +4 -0
- package/bash.exe.stackdump +40 -0
- package/components.json +21 -0
- package/dist/README.md +4 -0
- package/dist/assets/style.css +1 -0
- package/{elements → dist/elements}/DropdownMenu.d.ts +1 -0
- package/dist/examples/Dropdown.d.ts +2 -0
- package/{index.js → dist/index.js} +4 -2
- package/dist/package.json +30 -0
- package/eslint.config.js +92 -0
- package/index.html +13 -0
- package/package.json +39 -15
- package/postcss.config.cjs +19 -0
- package/public/mock.csv +16 -0
- package/public/mock_/345/211/257/346/234/254.csv +16 -0
- package/public/vite.svg +1 -0
- package/src/Preview.tsx +15 -0
- package/src/assets/img/excel.png +0 -0
- package/src/assets/img/img.png +0 -0
- package/src/assets/img/pdf.png +0 -0
- package/src/assets/img/ppt.png +0 -0
- package/src/assets/img/txt.png +0 -0
- package/src/assets/img/word.png +0 -0
- package/src/containers/DashboardContainer.tsx +507 -0
- package/src/containers/DockContainer.tsx +186 -0
- package/src/containers/style.css +37 -0
- package/src/elements/Button.tsx +118 -0
- package/src/elements/CustomDock.tsx +287 -0
- package/src/elements/DropDownButton.tsx +249 -0
- package/src/elements/DropdownMenu.tsx +186 -0
- package/src/elements/GoggleNavbar.tsx +184 -0
- package/src/elements/Uploader/README.md +249 -0
- package/src/elements/Uploader/UploadItem.tsx +298 -0
- package/src/elements/Uploader/example.tsx +95 -0
- package/src/elements/Uploader/index.tsx +702 -0
- package/src/elements/Uploader/styles.tsx +291 -0
- package/src/elements/Uploader/types.ts +119 -0
- package/src/elements/Uploader/utils.ts +200 -0
- package/src/elements/Uploader.tsx +3 -0
- package/src/examples/DockContainerExample.tsx +254 -0
- package/src/examples/Dropdown.tsx +27 -0
- package/src/icons/Icon.tsx +82 -0
- package/src/icons/index.tsx +92 -0
- package/src/icons/lazyIndex.tsx +49 -0
- package/src/icons/rag/csv.svg +3 -0
- package/src/icons/rag/document.svg +3 -0
- package/src/icons/rag/excel.svg +3 -0
- package/src/icons/rag/file.svg +3 -0
- package/src/icons/rag/folder.svg +5 -0
- package/src/icons/rag/json.svg +3 -0
- package/src/icons/rag/knowledgebase.svg +3 -0
- package/src/icons/rag/netretrive.svg +3 -0
- package/src/icons/rag/odf.svg +7 -0
- package/src/icons/rag/pdf.svg +3 -0
- package/src/icons/rag/pic.svg +3 -0
- package/src/icons/rag/ppt.svg +3 -0
- package/src/icons/rag/think.svg +6 -0
- package/src/icons/rag/txt.svg +3 -0
- package/src/icons/rag/url.svg +3 -0
- package/src/icons/rag/word.svg +3 -0
- package/src/icons/rag/wps.svg +3 -0
- package/src/icons/rag/zip.svg +7 -0
- package/src/lib_enter.ts +27 -0
- package/src/main.tsx +11 -0
- package/src/style.css +9 -0
- package/src/theme/styledTheme.tsx +253 -0
- package/src/type.d.ts +0 -0
- package/src/types/images.d.ts +12 -0
- package/src/types/svg-modules.d.ts +24 -0
- package/src/vite-env.d.ts +11 -0
- package/tailwind.config.js +170 -0
- package/tsconfig.app.json +29 -0
- package/tsconfig.app.tsbuildinfo +11 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +22 -0
- package/tsconfig.node.tsbuildinfo +1 -0
- package/vite.config.ts +165 -0
- package/assets/style.css +0 -1
- /package/{Preview.d.ts → dist/Preview.d.ts} +0 -0
- /package/{containers → dist/containers}/DashboardContainer.d.ts +0 -0
- /package/{containers → dist/containers}/DockContainer.d.ts +0 -0
- /package/{elements → dist/elements}/Button.d.ts +0 -0
- /package/{elements → dist/elements}/CustomDock.d.ts +0 -0
- /package/{elements → dist/elements}/DropDownButton.d.ts +0 -0
- /package/{elements → dist/elements}/GoggleNavbar.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/UploadItem.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/example.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/index.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/styles.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/types.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader/utils.d.ts +0 -0
- /package/{elements → dist/elements}/Uploader.d.ts +0 -0
- /package/{examples → dist/examples}/DockContainerExample.d.ts +0 -0
- /package/{icons → dist/icons}/Icon.d.ts +0 -0
- /package/{icons → dist/icons}/Icon.tsx +0 -0
- /package/{icons → dist/icons}/index.d.ts +0 -0
- /package/{icons → dist/icons}/index.tsx +0 -0
- /package/{icons → dist/icons}/lazyIndex.d.ts +0 -0
- /package/{icons → dist/icons}/lazyIndex.tsx +0 -0
- /package/{icons → dist/icons}/rag/csv.svg +0 -0
- /package/{icons → dist/icons}/rag/document.svg +0 -0
- /package/{icons → dist/icons}/rag/excel.svg +0 -0
- /package/{icons → dist/icons}/rag/file.svg +0 -0
- /package/{icons → dist/icons}/rag/folder.svg +0 -0
- /package/{icons → dist/icons}/rag/json.svg +0 -0
- /package/{icons → dist/icons}/rag/knowledgebase.svg +0 -0
- /package/{icons → dist/icons}/rag/netretrive.svg +0 -0
- /package/{icons → dist/icons}/rag/odf.svg +0 -0
- /package/{icons → dist/icons}/rag/pdf.svg +0 -0
- /package/{icons → dist/icons}/rag/pic.svg +0 -0
- /package/{icons → dist/icons}/rag/ppt.svg +0 -0
- /package/{icons → dist/icons}/rag/think.svg +0 -0
- /package/{icons → dist/icons}/rag/txt.svg +0 -0
- /package/{icons → dist/icons}/rag/url.svg +0 -0
- /package/{icons → dist/icons}/rag/word.svg +0 -0
- /package/{icons → dist/icons}/rag/wps.svg +0 -0
- /package/{icons → dist/icons}/rag/zip.svg +0 -0
- /package/{lib_enter.d.ts → dist/lib_enter.d.ts} +0 -0
- /package/{main.d.ts → dist/main.d.ts} +0 -0
- /package/{mock.csv → dist/mock.csv} +0 -0
- /package/{mock_ → dist/mock_}/345/211/257/346/234/254.csv" +0 -0
- /package/{theme → dist/theme}/styledTheme.d.ts +0 -0
- /package/{vite.svg → dist/vite.svg} +0 -0
package/public/mock.csv
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id,name,age,gender,email,phone,address,company,position,salary
|
|
2
|
+
1,张三,28,男,zhangsan@example.com,13800000001,北京市朝阳区,字节跳动,前端工程师,18000
|
|
3
|
+
2,李四,32,女,lis4@example.com,13800000002,上海市浦东新区,阿里巴巴,产品经理,22000
|
|
4
|
+
3,王五,25,男,wangwu@example.com,13800000003,深圳市南山区,腾讯,后端工程师,17000
|
|
5
|
+
4,赵六,29,女,zhaoliu@example.com,13800000004,杭州市西湖区,网易,UI设计师,16000
|
|
6
|
+
5,钱七,35,男,qianqi@example.com,13800000005,广州市天河区,美团,测试工程师,15000
|
|
7
|
+
6,孙八,27,女,sunba@example.com,13800000006,成都市高新区,京东,数据分析师,20000
|
|
8
|
+
7,周九,31,男,zhoujiu@example.com,13800000007,南京市鼓楼区,小米,运维工程师,15500
|
|
9
|
+
8,吴十,26,女,wushi@example.com,13800000008,武汉市武昌区,拼多多,市场专员,14000
|
|
10
|
+
9,郑十一,30,男,zheng11@example.com,13800000009,重庆市渝中区,百度,算法工程师,23000
|
|
11
|
+
10,冯十二,33,女,feng12@example.com,13800000010,苏州市工业园区,携程,HR,13500
|
|
12
|
+
11,陈十三,24,男,chen13@example.com,13800000011,天津市和平区,滴滴,前端工程师,17500
|
|
13
|
+
12,褚十四,28,女,chu14@example.com,13800000012,青岛市市南区,快手,产品经理,21000
|
|
14
|
+
13,卫十五,29,男,wei15@example.com,13800000013,厦门市思明区,知乎,后端工程师,16500
|
|
15
|
+
14,蒋十六,27,女,jiang16@example.com,13800000014,合肥市包河区,B站,UI设计师,15800
|
|
16
|
+
15,沈十七,34,男,shen17@example.com,13800000015,福州市鼓楼区,字节跳动,测试工程师,15200
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id,name,age,gender,email,phone,address,company,position,salary
|
|
2
|
+
1,张三,28,男,zhangsan@example.com,13800000001,北京市朝阳区,字节跳动,前端工程师,18000
|
|
3
|
+
2,李四,32,女,lis4@example.com,13800000002,上海市浦东新区,阿里巴巴,产品经理,22000
|
|
4
|
+
3,王五,25,男,wangwu@example.com,13800000003,深圳市南山区,腾讯,后端工程师,17000
|
|
5
|
+
4,赵六,29,女,zhaoliu@example.com,13800000004,杭州市西湖区,网易,UI设计师,16000
|
|
6
|
+
5,钱七,35,男,qianqi@example.com,13800000005,广州市天河区,美团,测试工程师,15000
|
|
7
|
+
6,孙八,27,女,sunba@example.com,13800000006,成都市高新区,京东,数据分析师,20000
|
|
8
|
+
7,周九,31,男,zhoujiu@example.com,13800000007,南京市鼓楼区,小米,运维工程师,15500
|
|
9
|
+
8,吴十,26,女,wushi@example.com,13800000008,武汉市武昌区,拼多多,市场专员,14000
|
|
10
|
+
9,郑十一,30,男,zheng11@example.com,13800000009,重庆市渝中区,百度,算法工程师,23000
|
|
11
|
+
10,冯十二,33,女,feng12@example.com,13800000010,苏州市工业园区,携程,HR,13500
|
|
12
|
+
11,陈十三,24,男,chen13@example.com,13800000011,天津市和平区,滴滴,前端工程师,17500
|
|
13
|
+
12,褚十四,28,女,chu14@example.com,13800000012,青岛市市南区,快手,产品经理,21000
|
|
14
|
+
13,卫十五,29,男,wei15@example.com,13800000013,厦门市思明区,知乎,后端工程师,16500
|
|
15
|
+
14,蒋十六,27,女,jiang16@example.com,13800000014,合肥市包河区,B站,UI设计师,15800
|
|
16
|
+
15,沈十七,34,男,shen17@example.com,13800000015,福州市鼓楼区,字节跳动,测试工程师,15200
|
package/public/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/src/Preview.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './style.css';
|
|
3
|
+
import DockContainerExample from './examples/DockContainerExample';
|
|
4
|
+
import Dropdown from './examples/Dropdown';
|
|
5
|
+
|
|
6
|
+
const Preview: React.FC = () => {
|
|
7
|
+
return (
|
|
8
|
+
<div className="w-screen h-screen relative">
|
|
9
|
+
{/* <DockContainerExample /> */}
|
|
10
|
+
<Dropdown />
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default Preview;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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;
|