@mxmweb/zui 1.1.2 → 1.1.10

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/README.md +0 -4
  2. package/package.json +14 -39
  3. package/.editorconfig +0 -38
  4. package/.prettierignore +0 -16
  5. package/.prettierrc +0 -17
  6. package/.releaserc.json +0 -36
  7. package/CHANGELOG.md +0 -58
  8. package/CONTRIBUTING.md +0 -111
  9. package/NPMREADME.md +0 -0
  10. package/bash.exe.stackdump +0 -40
  11. package/components.json +0 -21
  12. package/dist/README.md +0 -0
  13. package/dist/package.json +0 -26
  14. package/eslint.config.js +0 -92
  15. package/index.html +0 -13
  16. package/postcss.config.cjs +0 -19
  17. package/public/mock.csv +0 -16
  18. package/public/mock_/345/211/257/346/234/254.csv +0 -16
  19. package/public/vite.svg +0 -1
  20. package/src/Preview.tsx +0 -15
  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 +0 -507
  28. package/src/containers/DockContainer.tsx +0 -186
  29. package/src/containers/style.css +0 -37
  30. package/src/elements/Button.tsx +0 -118
  31. package/src/elements/CustomDock.tsx +0 -287
  32. package/src/elements/DropDownButton.tsx +0 -249
  33. package/src/elements/DropdownMenu.tsx +0 -184
  34. package/src/elements/GoggleNavbar.tsx +0 -184
  35. package/src/elements/Uploader/README.md +0 -249
  36. package/src/elements/Uploader/UploadItem.tsx +0 -298
  37. package/src/elements/Uploader/example.tsx +0 -95
  38. package/src/elements/Uploader/index.tsx +0 -702
  39. package/src/elements/Uploader/styles.tsx +0 -291
  40. package/src/elements/Uploader/types.ts +0 -119
  41. package/src/elements/Uploader/utils.ts +0 -200
  42. package/src/elements/Uploader.tsx +0 -3
  43. package/src/examples/DockContainerExample.tsx +0 -237
  44. package/src/icons/Icon.tsx +0 -82
  45. package/src/icons/index.tsx +0 -92
  46. package/src/icons/lazyIndex.tsx +0 -49
  47. package/src/icons/rag/csv.svg +0 -3
  48. package/src/icons/rag/document.svg +0 -3
  49. package/src/icons/rag/excel.svg +0 -3
  50. package/src/icons/rag/file.svg +0 -3
  51. package/src/icons/rag/folder.svg +0 -5
  52. package/src/icons/rag/json.svg +0 -3
  53. package/src/icons/rag/knowledgebase.svg +0 -3
  54. package/src/icons/rag/netretrive.svg +0 -3
  55. package/src/icons/rag/odf.svg +0 -7
  56. package/src/icons/rag/pdf.svg +0 -3
  57. package/src/icons/rag/pic.svg +0 -3
  58. package/src/icons/rag/ppt.svg +0 -3
  59. package/src/icons/rag/think.svg +0 -6
  60. package/src/icons/rag/txt.svg +0 -3
  61. package/src/icons/rag/url.svg +0 -3
  62. package/src/icons/rag/word.svg +0 -3
  63. package/src/icons/rag/wps.svg +0 -3
  64. package/src/icons/rag/zip.svg +0 -7
  65. package/src/lib_enter.ts +0 -27
  66. package/src/main.tsx +0 -11
  67. package/src/style.css +0 -9
  68. package/src/theme/styledTheme.tsx +0 -253
  69. package/src/type.d.ts +0 -0
  70. package/src/types/images.d.ts +0 -12
  71. package/src/types/svg-modules.d.ts +0 -24
  72. package/src/vite-env.d.ts +0 -11
  73. package/tailwind.config.js +0 -170
  74. package/tsconfig.app.json +0 -29
  75. package/tsconfig.app.tsbuildinfo +0 -11
  76. package/tsconfig.json +0 -13
  77. package/tsconfig.node.json +0 -22
  78. package/tsconfig.node.tsbuildinfo +0 -1
  79. package/vite.config.ts +0 -180
  80. /package/{dist/Preview.d.ts → Preview.d.ts} +0 -0
  81. /package/{dist/assets → assets}/style.css +0 -0
  82. /package/{dist/containers → containers}/DashboardContainer.d.ts +0 -0
  83. /package/{dist/containers → containers}/DockContainer.d.ts +0 -0
  84. /package/{dist/elements → elements}/Button.d.ts +0 -0
  85. /package/{dist/elements → elements}/CustomDock.d.ts +0 -0
  86. /package/{dist/elements → elements}/DropDownButton.d.ts +0 -0
  87. /package/{dist/elements → elements}/DropdownMenu.d.ts +0 -0
  88. /package/{dist/elements → elements}/GoggleNavbar.d.ts +0 -0
  89. /package/{dist/elements → elements}/Uploader/UploadItem.d.ts +0 -0
  90. /package/{dist/elements → elements}/Uploader/example.d.ts +0 -0
  91. /package/{dist/elements → elements}/Uploader/index.d.ts +0 -0
  92. /package/{dist/elements → elements}/Uploader/styles.d.ts +0 -0
  93. /package/{dist/elements → elements}/Uploader/types.d.ts +0 -0
  94. /package/{dist/elements → elements}/Uploader/utils.d.ts +0 -0
  95. /package/{dist/elements → elements}/Uploader.d.ts +0 -0
  96. /package/{dist/examples → examples}/DockContainerExample.d.ts +0 -0
  97. /package/{dist/icons → icons}/Icon.d.ts +0 -0
  98. /package/{dist/icons → icons}/Icon.tsx +0 -0
  99. /package/{dist/icons → icons}/index.d.ts +0 -0
  100. /package/{dist/icons → icons}/index.tsx +0 -0
  101. /package/{dist/icons → icons}/lazyIndex.d.ts +0 -0
  102. /package/{dist/icons → icons}/lazyIndex.tsx +0 -0
  103. /package/{dist/icons → icons}/rag/csv.svg +0 -0
  104. /package/{dist/icons → icons}/rag/document.svg +0 -0
  105. /package/{dist/icons → icons}/rag/excel.svg +0 -0
  106. /package/{dist/icons → icons}/rag/file.svg +0 -0
  107. /package/{dist/icons → icons}/rag/folder.svg +0 -0
  108. /package/{dist/icons → icons}/rag/json.svg +0 -0
  109. /package/{dist/icons → icons}/rag/knowledgebase.svg +0 -0
  110. /package/{dist/icons → icons}/rag/netretrive.svg +0 -0
  111. /package/{dist/icons → icons}/rag/odf.svg +0 -0
  112. /package/{dist/icons → icons}/rag/pdf.svg +0 -0
  113. /package/{dist/icons → icons}/rag/pic.svg +0 -0
  114. /package/{dist/icons → icons}/rag/ppt.svg +0 -0
  115. /package/{dist/icons → icons}/rag/think.svg +0 -0
  116. /package/{dist/icons → icons}/rag/txt.svg +0 -0
  117. /package/{dist/icons → icons}/rag/url.svg +0 -0
  118. /package/{dist/icons → icons}/rag/word.svg +0 -0
  119. /package/{dist/icons → icons}/rag/wps.svg +0 -0
  120. /package/{dist/icons → icons}/rag/zip.svg +0 -0
  121. /package/{dist/index.js → index.js} +0 -0
  122. /package/{dist/lib_enter.d.ts → lib_enter.d.ts} +0 -0
  123. /package/{dist/main.d.ts → main.d.ts} +0 -0
  124. /package/{dist/mock.csv → mock.csv} +0 -0
  125. /package/{dist/mock_ → mock_}/345/211/257/346/234/254.csv" +0 -0
  126. /package/{dist/theme → theme}/styledTheme.d.ts +0 -0
  127. /package/{dist/vite.svg → vite.svg} +0 -0
@@ -1,184 +0,0 @@
1
- import * as React from 'react';
2
- import { useState, useRef, useEffect } from 'react';
3
- import { createPortal } from 'react-dom';
4
- import { MoreHorizontal } from 'lucide-react';
5
- import { defaultTheme, type AppTheme, type Styles } from '../theme/styledTheme';
6
-
7
- export interface DropdownMenuAction {
8
- key: string;
9
- label?: string;
10
- type?: 'divider' | 'action';
11
- icon?: React.ReactNode;
12
- onClick?: () => void;
13
- }
14
-
15
- interface DropdownMenuProps {
16
- actions: DropdownMenuAction[];
17
- onAction: (key: string) => void;
18
- direction?: 'top' | 'bottom';
19
- styles?: Styles;
20
- }
21
-
22
- const DropdownMenu: React.FC<DropdownMenuProps> = ({
23
- actions,
24
- onAction,
25
- direction = 'top',
26
- styles,
27
- }) => {
28
- const [open, setOpen] = useState(false);
29
- const btnRef = useRef<HTMLButtonElement>(null);
30
- const [menuStyle, setMenuStyle] = useState<React.CSSProperties>({});
31
- const menuRef = useRef<HTMLDivElement>(null);
32
-
33
- // 计算弹出层位置
34
- useEffect(() => {
35
- if (!open) return;
36
- const btn = btnRef.current;
37
- if (!btn) return;
38
- const rect = btn.getBoundingClientRect();
39
- const scrollX = window.scrollX || window.pageXOffset;
40
- const scrollY = window.scrollY || window.pageYOffset;
41
- const width = 128; // 菜单宽度
42
- const margin = 8;
43
- let style: React.CSSProperties = {
44
- position: 'absolute',
45
- left: rect.right - width + scrollX,
46
- width,
47
- zIndex: 9999,
48
- };
49
- if (direction === 'bottom') {
50
- style.top = rect.bottom + margin - 12 + scrollY;
51
- } else {
52
- const itemHeight = 30; // 单个菜单项高度
53
- const padding = 1; // 上下padding
54
- const menuHeight = actions.length * itemHeight + padding;
55
- style.top = rect.top - margin + 10 - menuHeight + scrollY;
56
- }
57
- setMenuStyle(style);
58
- }, [open, direction, actions]);
59
-
60
- // 点击外部关闭、滚动关闭
61
- useEffect(() => {
62
- if (!open) return;
63
- const handleClick = (e: MouseEvent) => {
64
- if (btnRef.current && btnRef.current.contains(e.target as Node)) return;
65
- if (menuRef.current && menuRef.current.contains(e.target as Node)) return;
66
- setOpen(false);
67
- };
68
- const handleScroll = () => setOpen(false);
69
- document.addEventListener('mousedown', handleClick);
70
- window.addEventListener('scroll', handleScroll, true);
71
- return () => {
72
- document.removeEventListener('mousedown', handleClick);
73
- window.removeEventListener('scroll', handleScroll, true);
74
- };
75
- }, [open]);
76
-
77
- // 使用默认主题作为兜底
78
- const theme = styles?.theme || defaultTheme;
79
-
80
- // 菜单内容
81
- const menu = (
82
- <div
83
- ref={menuRef}
84
- className={`origin-bottom-right absolute right-0 w-32 rounded-md shadow-lg focus:outline-none border transition-all duration-200 ease-in-out
85
- ${open ? 'opacity-100 translate-y-0 pointer-events-auto scale-100' : 'opacity-0 pointer-events-none scale-95'}
86
- `}
87
- style={{
88
- ...menuStyle,
89
- background: theme.colors.background,
90
- color: theme.colors.text,
91
- border: `1px solid ${theme.colors.border}`,
92
- borderRadius: theme.space.radius,
93
- boxShadow: theme.colors.shadow,
94
- minWidth: 128,
95
- zIndex: 9999,
96
- }}
97
- >
98
- <div className="py-1 px-2">
99
- {actions.map((action, index) => {
100
- // 处理divider类型
101
- if (action.type === 'divider') {
102
- return (
103
- <div
104
- key={action.key || `divider-${index}`}
105
- className="my-2 bg-gray-100"
106
- style={{
107
- height: 1,
108
- }}
109
- />
110
- );
111
- }
112
-
113
- // 处理普通action类型
114
- return (
115
- <button
116
- key={action.key || `action-${index}`}
117
- className="w-full rounded flex cursor-pointer items-center px-4 py-2 text-sm transition whitespace-nowrap"
118
- style={{
119
- color: theme.colors.text,
120
- background: 'transparent',
121
- border: 'none',
122
- // borderRadius: theme.space.radius,
123
- fontSize: 12,
124
- }}
125
- onClick={e => {
126
- e.stopPropagation();
127
- setOpen(false);
128
- onAction(action.key);
129
- if (action.onClick) action.onClick();
130
- }}
131
- type="button"
132
- onMouseOver={e =>
133
- (e.currentTarget.style.background = theme.colors.disabledBackground)
134
- }
135
- onMouseOut={e => (e.currentTarget.style.background = 'transparent')}
136
- >
137
- {action.icon && <span className="mr-2">{action.icon}</span>}
138
- {action.label}
139
- </button>
140
- );
141
- })}
142
- </div>
143
- </div>
144
- );
145
-
146
- return (
147
- <>
148
- <button
149
- ref={btnRef}
150
- className="card-action-more-btn cursor-pointer! flex items-center justify-center w-9 h-9 rounded-sm transition border-none outline-none"
151
- style={{
152
- background: 'transparent', // 默认透明背景
153
- color: theme.colors.primary,
154
- borderRadius: theme.space.radius,
155
- }}
156
- onClick={e => {
157
- e.stopPropagation();
158
- setOpen(v => !v);
159
- }}
160
- tabIndex={0}
161
- aria-label="更多操作"
162
- type="button"
163
- >
164
- <MoreHorizontal size={18} className="text-blue-500" />
165
- </button>
166
- {open && createPortal(menu, document.body)}
167
- </>
168
- );
169
- };
170
-
171
- export default DropdownMenu;
172
-
173
- if (typeof window !== 'undefined' && !document.getElementById('card-action-more-btn-style')) {
174
- const style = document.createElement('style');
175
- style.id = 'card-action-more-btn-style';
176
- style.innerHTML = `
177
- .card-action-more-btn:hover {
178
- background: #f5f6fa !important;
179
- color: #222 !important;
180
- box-shadow: 0 2px 8px 0 rgba(51,112,255,0.06);
181
- }
182
- `;
183
- document.head.appendChild(style);
184
- }
@@ -1,184 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import styled from 'styled-components';
3
- import { defaultTheme, deepMergeTheme, type AppTheme, type Styles } from '../theme/styledTheme';
4
-
5
- export interface GoggleNavItem {
6
- key: string | number;
7
- name: string;
8
- disabled?: boolean;
9
- icon?: string | React.ReactNode;
10
- activeIcon?: string | React.ReactNode;
11
- path?: string; // 用于激活态判断
12
- badge?: number;
13
- }
14
-
15
- export interface GoggleNavbarProps {
16
- items: GoggleNavItem[];
17
- /** 当前激活路径,控制激活态渲染 */
18
- activePath?: string;
19
- /** 初始展开状态(非受控) */
20
- defaultOpen?: boolean;
21
- /** 受控展开状态 */
22
- open?: boolean;
23
- /** 展开状态变化回调(用于受控) */
24
- onOpenChange?: (open: boolean) => void;
25
- /** 点击项回调 */
26
- onItemClick?: (item: GoggleNavItem) => void;
27
- /** 顶部 Logo 图,可以是图片地址或 ReactNode */
28
- logo?: string | React.ReactNode;
29
- className?: string;
30
- style?: React.CSSProperties;
31
- styles?: Styles;
32
- }
33
-
34
- const Container = styled.div<{ $theme: AppTheme }>`
35
- background-color: #ffffff;
36
- box-shadow: 0 2px 12px rgba(0,0,0,0.08);
37
- border-radius: ${p => p.$theme?.space?.radius || '8px'};
38
- height: 90vh;
39
- overflow: hidden;
40
- position: relative;
41
- `;
42
-
43
- const useNavIconStyles = () => {
44
- useEffect(() => {
45
- const styleId = 'goggle-nav-icon-styles';
46
- if (!document.getElementById(styleId)) {
47
- const styleEl = document.createElement('style');
48
- styleEl.id = styleId;
49
- styleEl.innerHTML = `
50
- :root { --color-gray: #64748b; --color-blue: #3b82f6; }
51
- .g-nav-item { position: relative; transition: all .3s ease; border-radius: 8px; }
52
- .g-nav-icon img { transition: all .3s ease; filter: grayscale(100%) brightness(100%) opacity(.7); }
53
- .g-nav-text { color: var(--color-gray); transition: color .3s ease; }
54
- .g-nav-item:not(.active):hover .g-nav-icon img { filter: sepia(100%) saturate(1000%) hue-rotate(200deg) brightness(80%) opacity(1); transform: scale(1.05); }
55
- .g-nav-item:not(.active):hover .g-nav-text { color: var(--color-blue); font-weight: 500; }
56
- .g-nav-item.active { background-color: rgba(219,234,254,0.6); }
57
- .g-nav-item.active .g-nav-icon img { filter: sepia(100%) saturate(1000%) hue-rotate(200deg) brightness(100%) opacity(1); transform: scale(1.05); }
58
- .g-nav-item.active .g-nav-text { color: var(--color-blue); opacity: .8; font-weight: 500; }
59
- .g-nav-item.active::before { content: ''; position: absolute; left: 0; top: 30%; bottom: 30%; width: 3px; background: var(--color-blue); border-radius: 0 2px 2px 0; }
60
- `;
61
- document.head.appendChild(styleEl);
62
- }
63
- }, []);
64
- };
65
-
66
- const GoggleNavbar: React.FC<GoggleNavbarProps> = ({
67
- items,
68
- activePath,
69
- defaultOpen = true,
70
- open,
71
- onOpenChange,
72
- onItemClick,
73
- logo,
74
- className,
75
- style,
76
- styles,
77
- }) => {
78
- const theme = deepMergeTheme(defaultTheme, styles?.theme);
79
- useNavIconStyles();
80
-
81
- const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen);
82
- const isOpen = typeof open === 'boolean' ? open : internalOpen;
83
- const setOpen = (val: boolean) => {
84
- if (typeof open === 'boolean') {
85
- onOpenChange?.(val);
86
- } else {
87
- setInternalOpen(val);
88
- onOpenChange?.(val);
89
- }
90
- };
91
- const [isHover, setIsHover] = useState(false);
92
-
93
- const renderIcon = (icon?: string | React.ReactNode) => {
94
- if (!icon) return null;
95
- if (typeof icon === 'string') {
96
- return <img src={icon} className="w-6 h-6 transition-all duration-200" alt="nav" />;
97
- }
98
- return icon;
99
- };
100
-
101
- const widthStyle = useMemo(() => ({
102
- width: isOpen ? '60px' : '20px',
103
- minWidth: isOpen ? '60px' : '20px',
104
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
105
- }), [isOpen]);
106
-
107
- return (
108
- <Container $theme={theme.theme || defaultTheme} className={`relative ${className || ''}`} style={{ ...widthStyle, ...style }}>
109
- {/* 折叠按钮(内置) */}
110
- <div
111
- onMouseEnter={() => setIsHover(true)}
112
- onMouseLeave={() => setIsHover(false)}
113
- className="absolute top-1/2 -translate-y-1/2 right-1 w-6 h-6 cursor-pointer z-20 transition-all duration-300"
114
- onClick={() => setOpen(!isOpen)}
115
- aria-label={isOpen ? '收起' : '展开'}
116
- role="button"
117
- >
118
- <div className="group flex items-center justify-center w-6 h-6 rounded-full transition-all duration-200">
119
- <div className="relative w-4 h-4 flex items-center justify-center">
120
- <div className={`absolute w-[2px] h-3 bg-gray-400 rounded-full transition-all duration-300 ${isHover ? 'opacity-0 scale-0' : 'opacity-100 scale-100'}`} />
121
- <svg
122
- viewBox="0 0 24 24"
123
- fill="none"
124
- className={`absolute w-4 h-4 stroke-gray-400 transition-all duration-300 ${isHover ? 'opacity-100 scale-100' : 'opacity-0 scale-0'} ${!isOpen ? 'rotate-180' : 'rotate-0'}`}
125
- strokeWidth="2"
126
- strokeLinecap="round"
127
- strokeLinejoin="round"
128
- >
129
- <polyline points="15 18 9 12 15 6" />
130
- </svg>
131
- </div>
132
- </div>
133
- </div>
134
-
135
- {/* 内容 */}
136
- <div
137
- className="h-full py-6 px-[2px] flex flex-col items-center"
138
- style={{
139
- opacity: isOpen ? 1 : 0,
140
- transform: isOpen ? 'translateX(0)' : 'translateX(-10px)',
141
- transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
142
- }}
143
- >
144
- {/* Logo */}
145
- <div className="mb-6 transition-transform duration-300 hover:scale-105">
146
- {typeof logo === 'string' ? (
147
- <img src={logo} className="w-7 h-7" alt="logo" />
148
- ) : (
149
- logo || <div className="w-7 h-7 rounded bg-gray-100 flex items-center justify-center text-xs text-gray-500">G</div>
150
- )}
151
- </div>
152
-
153
- {/* 导航项 */}
154
- <div className="w-full gap-y-[2px] mb-10 flex-1 overflow-x-hidden overflow-y-auto scrollbar-none flex flex-col items-center">
155
- {items.map(item => {
156
- const active = !!(activePath && item.path && activePath.startsWith(item.path));
157
- const iconNode = active && item.activeIcon ? item.activeIcon : item.icon;
158
- return (
159
- <div
160
- key={item.key}
161
- onClick={() => !item.disabled && onItemClick?.(item)}
162
- className={`g-nav-item group relative w-full flex flex-col items-center justify-center py-3 px-2 rounded-lg cursor-pointer transition-all duration-200 ${active ? 'active bg-blue-50' : 'text-gray-500 hover:bg-gray-50'}`}
163
- >
164
- <div className="relative g-nav-icon transition-transform duration-200 group-hover:scale-105">
165
- {renderIcon(iconNode)}
166
- {item.badge && item.badge > 0 && (
167
- <span className="absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full bg-red-500 text-white text-[10px] leading-4 text-center">
168
- {item.badge > 99 ? '99+' : item.badge}
169
- </span>
170
- )}
171
- </div>
172
- <div className="mt-1 text-xs text-center g-nav-text transition-colors duration-200">{item.name}</div>
173
- </div>
174
- );
175
- })}
176
- </div>
177
- </div>
178
- </Container>
179
- );
180
- };
181
-
182
- export default GoggleNavbar;
183
-
184
-
@@ -1,249 +0,0 @@
1
- # Uploader 文件上传组件
2
-
3
- ## 功能特性
4
-
5
- ### 🎯 核心功能
6
- - **拖拽上传**: 支持拖拽文件到指定区域进行上传
7
- - **多文件上传**: 支持同时选择多个文件
8
- - **独立表单管理**: 每个上传项都有独立的表单数据,互不干扰
9
- - **自动填充**: 上传完成后自动填充文件名到表单中
10
- - **数据持久化**: 表单展开/收起时保持数据状态,不会丢失
11
-
12
- ### 🔧 技术特性
13
- - **TypeScript**: 完整的类型支持
14
- - **Styled Components**: 基于主题的样式系统
15
- - **XViewer集成**: 使用XViewer的动态表单组件
16
- - **事件驱动**: 完整的事件回调系统
17
- - **模块化设计**: 组件拆分,便于维护
18
-
19
- ## 文件结构
20
-
21
- ```
22
- Uploader/
23
- ├── index.tsx # 主组件
24
- ├── UploadItem.tsx # 上传项组件
25
- ├── types.ts # 类型定义
26
- ├── utils.ts # 工具函数
27
- ├── styles.tsx # 样式组件
28
- ├── example.tsx # 使用示例
29
- └── README.md # 说明文档
30
- ```
31
-
32
- ## 使用方法
33
-
34
- ### 基础用法
35
-
36
- ```tsx
37
- import Uploader from './Uploader';
38
-
39
- const MyComponent = () => {
40
- return (
41
- <Uploader
42
- url="http://localhost:3000/api/upload"
43
- multiple={true}
44
- accept="*/*"
45
- maxSize={50}
46
- />
47
- );
48
- };
49
- ```
50
-
51
- ### 带表单配置的用法
52
-
53
- ```tsx
54
- import Uploader from './Uploader';
55
-
56
- const MyComponent = () => {
57
- // 表单配置
58
- const itemForm = [
59
- {
60
- name: 'name',
61
- label: '文件名',
62
- type: 'input',
63
- placeholder: '请输入文件名'
64
- },
65
- {
66
- name: 'description',
67
- label: '文件描述',
68
- type: 'textarea',
69
- placeholder: '请输入文件描述'
70
- },
71
- {
72
- name: 'category',
73
- label: '文件分类',
74
- type: 'select',
75
- options: [
76
- { label: '文档', value: 'document' },
77
- { label: '图片', value: 'image' },
78
- { label: '视频', value: 'video' }
79
- ]
80
- }
81
- ];
82
-
83
- // 事件处理
84
- const handleEvents = (eventName: string, data: any) => {
85
- console.log('Uploader Event:', eventName, data);
86
- };
87
-
88
- return (
89
- <Uploader
90
- url="http://localhost:3000/api/upload"
91
- itemForm={itemForm}
92
- eventsEmit={handleEvents}
93
- headers={{
94
- 'Authorization': 'Bearer your-token'
95
- }}
96
- />
97
- );
98
- };
99
- ```
100
-
101
- ## API 文档
102
-
103
- ### UploaderProps
104
-
105
- | 属性 | 类型 | 默认值 | 说明 |
106
- |------|------|--------|------|
107
- | `multiple` | `boolean` | `true` | 是否支持多文件选择 |
108
- | `accept` | `string` | `'*/*'` | 接受的文件类型 |
109
- | `maxSize` | `number` | `100` | 单个文件最大大小(MB) |
110
- | `maxFiles` | `number` | `10` | 最大文件数量 |
111
- | `autoUpload` | `boolean` | `true` | 是否自动上传 |
112
- | `url` | `string` | `'http://localhost:3000/upload'` | 上传接口地址 |
113
- | `headers` | `Record<string, string>` | `{}` | 自定义请求头 |
114
- | `queryParams` | `Record<string, string>` | `{}` | 查询参数 |
115
- | `itemForm` | `any[]` | `[]` | 每个上传项的表单配置 |
116
- | `styles` | `{ theme?: AppTheme }` | `{}` | 样式配置 |
117
- | `eventsEmit` | `(eventName: UploaderEventName, data: any) => void` | `undefined` | 事件回调 |
118
- | `onRef` | `(methods: UploaderMethods) => void` | `undefined` | 暴露方法给父组件 |
119
-
120
- ### 事件类型
121
-
122
- | 事件名 | 说明 | 数据格式 |
123
- |--------|------|----------|
124
- | `uploader:start` | 开始上传 | `{ items: UploadItem[] }` |
125
- | `uploader:progress` | 上传进度 | `{ item: UploadItem }` |
126
- | `uploader:success` | 上传成功 | `{ item: UploadItem }` |
127
- | `uploader:error` | 上传失败 | `{ item: UploadItem, error: string }` |
128
- | `uploader:complete` | 上传完成 | `{ items: UploadItem[] }` |
129
- | `uploader:formDataChange` | 表单数据变化 | `{ itemId: string, formData: Record<string, any>, item: UploadItem }` |
130
- | `uploader:toggleExpand` | 表单展开/收起 | `{ itemId: string, isExpanded: boolean, item: UploadItem }` |
131
- | `uploader:remove` | 移除文件 | `{ itemId: string, item: UploadItem }` |
132
- | `uploader:dataChange` | 数据变化 | `{ items: UploadItem[], changedItemId?: string, changedFormData?: Record<string, any> }` |
133
-
134
- ### UploaderMethods
135
-
136
- 通过 `onRef` 属性可以获取到以下方法:
137
-
138
- | 方法名 | 参数 | 返回值 | 说明 |
139
- |--------|------|--------|------|
140
- | `getUploadItems` | 无 | `UploadItem[]` | 获取所有上传项(包含所有状态) |
141
- | `getUploadItemsData` | 无 | `Array<{id, name, url, formData, file, size, type}>` | 获取所有成功上传项的数据(包含表单数据) |
142
- | `clearUploadItems` | 无 | `void` | 清空所有上传项 |
143
- | `addUploadItems` | `files: File[]` | `void` | 添加新的文件进行上传 |
144
- | `removeUploadItem` | `id: string` | `void` | 移除指定ID的上传项 |
145
- | `validateFormData` | 无 | `Promise<boolean>` | 验证表单数据(检查是否有错误、是否在上传中等) |
146
-
147
- ### 使用示例
148
-
149
- ```tsx
150
- import React, { useRef } from 'react';
151
- import Uploader, { type UploaderMethods } from './Uploader';
152
-
153
- const MyComponent = () => {
154
- const uploaderRef = useRef<UploaderMethods | null>(null);
155
-
156
- const handleSubmit = async () => {
157
- if (uploaderRef.current) {
158
- // 验证数据
159
- const isValid = await uploaderRef.current.validateFormData();
160
- if (!isValid) {
161
- alert('请检查上传数据');
162
- return;
163
- }
164
-
165
- // 获取所有上传数据
166
- const uploadData = uploaderRef.current.getUploadItemsData();
167
- console.log('提交的数据:', uploadData);
168
-
169
- // 发送到服务器
170
- await submitToServer(uploadData);
171
- }
172
- };
173
-
174
- return (
175
- <div>
176
- <Uploader
177
- onRef={(methods) => {
178
- uploaderRef.current = methods;
179
- }}
180
- // ... 其他属性
181
- />
182
- <button onClick={handleSubmit}>提交</button>
183
- </div>
184
- );
185
- };
186
- ```
187
-
188
- ## 自动填充功能
189
-
190
- 组件会自动识别表单字段名并填充相应的文件信息:
191
-
192
- ### 文件名字段
193
- - `name`, `filename`, `title`, `displayName`, `fileName`
194
-
195
- ### 文件大小字段
196
- - `size`, `fileSize`, `file_size`
197
-
198
- ### 文件类型字段
199
- - `type`, `fileType`, `mimeType`, `contentType`
200
-
201
- ## 独立表单数据管理
202
-
203
- 每个上传项都有独立的表单数据存储:
204
-
205
- ```tsx
206
- // 每个UploadItem都有独立的formData
207
- interface UploadItem {
208
- id: string;
209
- file: File;
210
- name: string;
211
- size: number;
212
- type: string;
213
- status: UploadStatus;
214
- progress: number;
215
- formData?: Record<string, any>; // 独立的表单数据
216
- isExpanded?: boolean;
217
- }
218
- ```
219
-
220
- ## 样式定制
221
-
222
- 组件支持通过主题进行样式定制:
223
-
224
- ```tsx
225
- import { defaultTheme } from '../../theme/styledTheme';
226
-
227
- const customTheme = {
228
- ...defaultTheme,
229
- colors: {
230
- ...defaultTheme.colors,
231
- primary: '#007bff',
232
- success: '#28a745',
233
- error: '#dc3545'
234
- }
235
- };
236
-
237
- <Uploader
238
- styles={{ theme: customTheme }}
239
- // ... 其他属性
240
- />
241
- ```
242
-
243
- ## 注意事项
244
-
245
- 1. **上传URL**: 必须提供有效的上传接口地址
246
- 2. **表单配置**: 使用XViewer的表单配置格式
247
- 3. **文件大小**: 注意设置合理的文件大小限制
248
- 4. **网络请求**: 组件使用XMLHttpRequest进行文件上传
249
- 5. **数据持久化**: 表单数据在组件状态中保持,页面刷新会丢失