@wzyjs/uis 0.3.32 → 0.3.37

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.
@@ -1,22 +1,25 @@
1
- import { type ReactElement } from 'react';
1
+ import { type ReactElement, type ReactNode } from 'react';
2
2
  import type { UploadProps, UploadFile as AntUploadFile } from 'antd';
3
+ import { type UploadRequestData } from './utils';
3
4
  export interface FileUploaderProps {
4
5
  value?: AntUploadFile[];
5
6
  onChange?: (fileList: AntUploadFile[]) => void;
6
7
  mode?: 'file' | 'http';
7
- accept?: string;
8
- multiple?: boolean;
9
- maxCount?: number;
8
+ accept?: UploadProps['accept'];
9
+ multiple?: UploadProps['multiple'];
10
+ maxCount?: UploadProps['maxCount'];
10
11
  trigger?: 'button' | 'dragger' | ReactElement;
12
+ children?: ReactNode;
13
+ className?: UploadProps['className'];
11
14
  listType?: UploadProps['listType'];
12
- beforeUpload?: (file: AntUploadFile) => boolean | Promise<void>;
15
+ beforeUpload?: UploadProps['beforeUpload'];
13
16
  immediateUpload?: boolean;
14
17
  action?: string;
15
- data?: Record<string, unknown> | ((file: AntUploadFile) => Record<string, unknown>);
18
+ data?: UploadRequestData;
16
19
  headers?: UploadProps['headers'];
17
20
  withCredentials?: boolean;
18
21
  disabled?: boolean;
19
- showUploadList?: boolean;
22
+ showUploadList?: UploadProps['showUploadList'];
20
23
  onUploadChange?: UploadProps['onChange'];
21
24
  }
22
25
  export declare const FileUploader: (props: FileUploaderProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,11 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState } from 'react';
4
- import { Upload, Button, } from 'antd';
4
+ import { Upload, Button } from 'antd';
5
5
  import { UploadOutlined, InboxOutlined } from '@ant-design/icons';
6
+ import { handleUploadBefore } from './utils';
6
7
  export const FileUploader = (props) => {
7
- const { value, onChange, mode = 'file', accept, multiple = true, maxCount, trigger = 'dragger', listType = 'text', beforeUpload, immediateUpload = false, action = '/api/file/upload', data, headers, withCredentials, disabled, showUploadList = trigger === 'dragger', onUploadChange, } = props;
8
+ const { value, onChange, mode = 'file', accept, multiple = true, maxCount, trigger = 'dragger', children, className, listType = 'text', beforeUpload, immediateUpload = false, action = '/api/file/upload', data, headers, withCredentials, disabled, showUploadList = trigger === 'dragger', onUploadChange, } = props;
8
9
  const [internalFileList, setInternalFileList] = useState([]);
9
10
  const fileList = value || internalFileList;
10
11
  const handleChange = (info) => {
@@ -26,20 +27,13 @@ export const FileUploader = (props) => {
26
27
  onChange?.(newFileList);
27
28
  onUploadChange?.(info);
28
29
  };
29
- const handleBeforeUpload = async (file) => {
30
- // 1. 外部校验
31
- if (beforeUpload) {
32
- const result = await beforeUpload(file);
33
- if (result === false) {
34
- return Upload.LIST_IGNORE;
35
- }
36
- }
37
- // 2. 立即上传模式
38
- if (immediateUpload) {
39
- return true;
40
- }
41
- // 3. 非立即上传模式,阻止默认上传行为,但添加到列表
42
- return false;
30
+ const handleBeforeUpload = async (file, currentFileList) => {
31
+ return await handleUploadBefore({
32
+ beforeUpload,
33
+ file,
34
+ currentFileList,
35
+ immediateUpload,
36
+ });
43
37
  };
44
38
  const uploadProps = {
45
39
  fileList,
@@ -47,6 +41,7 @@ export const FileUploader = (props) => {
47
41
  multiple,
48
42
  maxCount,
49
43
  listType,
44
+ className,
50
45
  disabled,
51
46
  showUploadList,
52
47
  beforeUpload: handleBeforeUpload,
@@ -70,10 +65,10 @@ export const FileUploader = (props) => {
70
65
  return _jsx("div", { children: "HTTP \u8F93\u5165\u6A21\u5F0F\u6682\u672A\u652F\u6301" });
71
66
  }
72
67
  if (trigger === 'button') {
73
- return (_jsx(Upload, { ...uploadProps, children: _jsx(Button, { type: "primary", icon: _jsx(UploadOutlined, {}), children: "\u4E0A\u4F20\u6587\u4EF6" }) }));
68
+ return (_jsx(Upload, { ...uploadProps, children: _jsx(Button, { type: 'primary', icon: _jsx(UploadOutlined, {}), children: "\u4E0A\u4F20\u6587\u4EF6" }) }));
74
69
  }
75
70
  if (trigger === 'dragger') {
76
- return (_jsxs(Upload.Dragger, { ...uploadProps, children: [_jsx("p", { className: "ant-upload-drag-icon", children: _jsx(InboxOutlined, {}) }), _jsx("p", { className: "ant-upload-text", children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }), _jsxs("p", { className: "ant-upload-hint", children: ["\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", maxCount ? `,最多 ${maxCount} 个文件` : ''] })] }));
71
+ return (_jsx(Upload.Dragger, { ...uploadProps, children: children ?? (_jsxs(_Fragment, { children: [_jsx("p", { className: 'ant-upload-drag-icon', children: _jsx(InboxOutlined, {}) }), _jsx("p", { className: 'ant-upload-text', children: "\u70B9\u51FB\u6216\u62D6\u62FD\u6587\u4EF6\u5230\u6B64\u533A\u57DF\u4E0A\u4F20" }), _jsxs("p", { className: 'ant-upload-hint', children: ["\u652F\u6301\u5355\u4E2A\u6216\u6279\u91CF\u4E0A\u4F20", maxCount ? `,最多 ${maxCount} 个文件` : ''] })] })) }));
77
72
  }
78
73
  return trigger;
79
74
  };
@@ -0,0 +1,20 @@
1
+ import type { UploadFile as AntUploadFile, UploadProps } from 'antd';
2
+ export type UploadRequestData = Record<string, unknown> | ((file: AntUploadFile) => Record<string, unknown>);
3
+ type BeforeUploadFile = Parameters<NonNullable<UploadProps['beforeUpload']>>[0];
4
+ type BeforeUploadFileList = Parameters<NonNullable<UploadProps['beforeUpload']>>[1];
5
+ export declare const createAntUploadFile: (file: File) => AntUploadFile;
6
+ export declare const handleUploadBefore: ({ beforeUpload, file, currentFileList, immediateUpload, }: {
7
+ beforeUpload?: UploadProps["beforeUpload"];
8
+ file: BeforeUploadFile;
9
+ currentFileList: BeforeUploadFileList;
10
+ immediateUpload: boolean;
11
+ }) => Promise<string | boolean>;
12
+ export declare const uploadFileByAction: ({ action, file, data, headers, withCredentials, }: {
13
+ action: string;
14
+ file: File;
15
+ data?: UploadRequestData;
16
+ headers?: UploadProps["headers"];
17
+ withCredentials?: boolean;
18
+ }) => Promise<any>;
19
+ export declare const getUploadedUrl: (response: unknown) => string;
20
+ export {};
@@ -0,0 +1,99 @@
1
+ import { Upload } from 'antd';
2
+ export const createAntUploadFile = (file) => ({
3
+ uid: `${file.name}_${file.lastModified}`,
4
+ name: file.name,
5
+ size: file.size,
6
+ type: file.type,
7
+ lastModified: file.lastModified,
8
+ lastModifiedDate: new Date(file.lastModified),
9
+ originFileObj: file,
10
+ });
11
+ export const handleUploadBefore = async ({ beforeUpload, file, currentFileList, immediateUpload, }) => {
12
+ if (beforeUpload) {
13
+ const result = await beforeUpload(file, currentFileList);
14
+ if (result === false || result === Upload.LIST_IGNORE) {
15
+ return Upload.LIST_IGNORE;
16
+ }
17
+ }
18
+ if (immediateUpload) {
19
+ return true;
20
+ }
21
+ return false;
22
+ };
23
+ const resolveUploadData = (data, file) => {
24
+ if (!data) {
25
+ return {};
26
+ }
27
+ if (typeof data === 'function') {
28
+ return data(createAntUploadFile(file));
29
+ }
30
+ return data;
31
+ };
32
+ const appendFormValue = (formData, key, value) => {
33
+ if (value == null) {
34
+ return;
35
+ }
36
+ if (value instanceof Blob) {
37
+ formData.append(key, value);
38
+ return;
39
+ }
40
+ if (typeof value === 'object') {
41
+ formData.append(key, JSON.stringify(value));
42
+ return;
43
+ }
44
+ formData.append(key, String(value));
45
+ };
46
+ const normalizeHeaders = (headers) => {
47
+ if (!headers) {
48
+ return undefined;
49
+ }
50
+ const normalizedHeaders = new Headers();
51
+ Object.entries(headers).forEach(([key, value]) => {
52
+ if (value != null) {
53
+ normalizedHeaders.set(key, String(value));
54
+ }
55
+ });
56
+ return normalizedHeaders;
57
+ };
58
+ const getUploadErrorMessage = async (response) => {
59
+ const contentType = response.headers.get('content-type') || '';
60
+ if (contentType.includes('application/json')) {
61
+ const body = await response.json().catch(() => null);
62
+ if (body && typeof body === 'object' && 'error' in body && typeof body.error === 'string') {
63
+ return body.error;
64
+ }
65
+ }
66
+ const text = await response.text().catch(() => '');
67
+ if (text) {
68
+ return text;
69
+ }
70
+ return `上传失败: ${response.status}`;
71
+ };
72
+ export const uploadFileByAction = async ({ action, file, data, headers, withCredentials, }) => {
73
+ const formData = new FormData();
74
+ formData.append('file', file);
75
+ const uploadData = resolveUploadData(data, file);
76
+ Object.entries(uploadData).forEach(([key, value]) => {
77
+ appendFormValue(formData, key, value);
78
+ });
79
+ const response = await fetch(action, {
80
+ method: 'POST',
81
+ body: formData,
82
+ headers: normalizeHeaders(headers),
83
+ credentials: withCredentials ? 'include' : 'same-origin',
84
+ });
85
+ if (!response.ok) {
86
+ throw new Error(await getUploadErrorMessage(response));
87
+ }
88
+ return await response.json();
89
+ };
90
+ export const getUploadedUrl = (response) => {
91
+ if (response
92
+ && typeof response === 'object'
93
+ && 'url' in response
94
+ && typeof response.url === 'string'
95
+ && response.url) {
96
+ return response.url;
97
+ }
98
+ throw new Error('上传接口返回缺少 url 字段');
99
+ };
@@ -1,3 +1,5 @@
1
+ import type { UploadProps } from 'antd';
2
+ import { type UploadRequestData } from '../FileUploader/utils';
1
3
  interface ImageUploaderProps {
2
4
  value?: string[];
3
5
  onChange?: (value: string[]) => void;
@@ -7,6 +9,13 @@ interface ImageUploaderProps {
7
9
  enableFileSelect?: boolean;
8
10
  enablePaste?: boolean;
9
11
  enablePasteTip?: boolean;
12
+ beforeUpload?: UploadProps['beforeUpload'];
13
+ immediateUpload?: boolean;
14
+ action?: string;
15
+ data?: UploadRequestData;
16
+ headers?: UploadProps['headers'];
17
+ withCredentials?: boolean;
18
+ onUploadChange?: UploadProps['onChange'];
10
19
  }
11
20
  export declare const ImageUploader: (props: ImageUploaderProps) => import("react/jsx-runtime").JSX.Element;
12
21
  export {};
@@ -3,60 +3,206 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { Alert, Button, Modal, Popover, Space, Typography } from 'antd';
5
5
  import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
6
+ import { createAntUploadFile, getUploadedUrl, handleUploadBefore, uploadFileByAction, } from '../FileUploader/utils';
7
+ const createImageFile = (file, index) => {
8
+ if (file instanceof File) {
9
+ return file;
10
+ }
11
+ return new File([file], `image_${Date.now()}_${index}.png`, {
12
+ type: file.type || 'image/png',
13
+ lastModified: Date.now(),
14
+ });
15
+ };
16
+ const createPreviewFileList = (images) => {
17
+ return images.map((image, index) => ({
18
+ uid: `${image}_${index}`,
19
+ name: `image_${index + 1}`,
20
+ status: 'done',
21
+ url: image,
22
+ }));
23
+ };
24
+ const readAsDataURL = (file) => {
25
+ return new Promise((resolve, reject) => {
26
+ const reader = new FileReader();
27
+ reader.onloadend = () => resolve(reader.result);
28
+ reader.onerror = () => reject(reader.error || new Error('读取图片失败'));
29
+ reader.readAsDataURL(file);
30
+ });
31
+ };
6
32
  export const ImageUploader = (props) => {
7
- const { value = [], onChange, maxImages = 10, enablePreview = false, enableDragDrop = false, enableFileSelect = false, enablePaste = true, enablePasteTip = false, } = props;
33
+ const { value = [], onChange, maxImages = 10, enablePreview = false, enableDragDrop = false, enableFileSelect = false, enablePaste = true, enablePasteTip = false, beforeUpload, immediateUpload = false, action = '/api/file/upload', data, headers, withCredentials, onUploadChange, } = props;
8
34
  const [isDragging, setIsDragging] = useState(false);
9
35
  const fileInputRef = useRef(null);
36
+ const compressImage = (file) => {
37
+ return new Promise((resolve) => {
38
+ const originalSize = file.size / 1024;
39
+ const img = new Image();
40
+ img.src = URL.createObjectURL(file);
41
+ img.onload = () => {
42
+ URL.revokeObjectURL(img.src);
43
+ const canvas = document.createElement('canvas');
44
+ let width = img.width;
45
+ let height = img.height;
46
+ const maxWidth = 1200;
47
+ const maxHeight = 1200;
48
+ if (width > maxWidth || height > maxHeight) {
49
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
50
+ width = width * ratio;
51
+ height = height * ratio;
52
+ }
53
+ canvas.width = width;
54
+ canvas.height = height;
55
+ const ctx = canvas.getContext('2d');
56
+ if (!ctx) {
57
+ resolve(file);
58
+ return;
59
+ }
60
+ ctx.drawImage(img, 0, 0, width, height);
61
+ canvas.toBlob((blob) => {
62
+ if (!blob) {
63
+ resolve(file);
64
+ return;
65
+ }
66
+ const compressedSize = blob.size / 1024;
67
+ console.log(`原始图片: ${originalSize.toFixed(2)} KB => ${compressedSize.toFixed(2)} KB`);
68
+ console.log(`压缩到了: ${((compressedSize / originalSize) * 100).toFixed(2)}%`);
69
+ resolve(blob);
70
+ }, 'image/jpeg', 0.8);
71
+ };
72
+ });
73
+ };
74
+ const triggerUploadChange = (file, images) => {
75
+ onUploadChange?.({
76
+ file,
77
+ fileList: createPreviewFileList(images),
78
+ });
79
+ };
80
+ const appendImage = (nextImages, image) => {
81
+ if (!nextImages.includes(image)) {
82
+ nextImages.push(image);
83
+ onChange?.(nextImages.slice());
84
+ }
85
+ };
86
+ const uploadImage = async (file) => {
87
+ const compressed = await compressImage(file);
88
+ const uploadFile = new File([compressed], file.name, {
89
+ type: compressed.type || file.type || 'image/jpeg',
90
+ lastModified: file.lastModified,
91
+ });
92
+ const response = await uploadFileByAction({
93
+ action,
94
+ file: uploadFile,
95
+ data,
96
+ headers,
97
+ withCredentials,
98
+ });
99
+ return {
100
+ response,
101
+ url: getUploadedUrl(response),
102
+ uploadFile,
103
+ };
104
+ };
105
+ const addFiles = async (files) => {
106
+ const remainCount = maxImages - value.length;
107
+ if (remainCount <= 0) {
108
+ alert(`最多允许上传 ${maxImages} 张图片。`);
109
+ return;
110
+ }
111
+ const imageFiles = files
112
+ .filter(file => file.type.startsWith('image/'))
113
+ .slice(0, remainCount)
114
+ .map((file, index) => createImageFile(file, index));
115
+ const nextImages = value.slice();
116
+ try {
117
+ for (const file of imageFiles) {
118
+ const beforeResult = await handleUploadBefore({
119
+ beforeUpload,
120
+ file: file,
121
+ currentFileList: imageFiles,
122
+ immediateUpload,
123
+ });
124
+ if (beforeResult !== true && beforeResult !== false) {
125
+ continue;
126
+ }
127
+ if (immediateUpload) {
128
+ const { response, url, uploadFile } = await uploadImage(file);
129
+ appendImage(nextImages, url);
130
+ triggerUploadChange({
131
+ ...createAntUploadFile(uploadFile),
132
+ status: 'done',
133
+ url,
134
+ response,
135
+ }, nextImages);
136
+ continue;
137
+ }
138
+ const compressed = await compressImage(file);
139
+ const base64String = await readAsDataURL(compressed);
140
+ appendImage(nextImages, base64String);
141
+ }
142
+ }
143
+ catch (error) {
144
+ console.error('图片处理失败:', error);
145
+ if (immediateUpload) {
146
+ triggerUploadChange({
147
+ uid: '-1',
148
+ name: 'upload-error',
149
+ status: 'error',
150
+ }, nextImages);
151
+ }
152
+ alert(error instanceof Error ? error.message : '图片处理失败,请重试。');
153
+ }
154
+ finally {
155
+ if (fileInputRef.current) {
156
+ fileInputRef.current.value = '';
157
+ }
158
+ }
159
+ };
10
160
  useEffect(() => {
11
161
  if (!enablePaste)
12
162
  return;
13
163
  const handlePaste = (event) => {
14
- if (event.clipboardData && event.clipboardData.items) {
15
- const items = event.clipboardData.items;
16
- for (let i = 0; i < items.length; i++) {
17
- const item = items[i];
18
- if (item && item.type.indexOf('image') !== -1) {
19
- const file = item.getAsFile();
20
- if (file) {
21
- convertToBase64(file);
22
- }
164
+ const items = event.clipboardData?.items || [];
165
+ const files = [];
166
+ for (const item of items) {
167
+ if (item.type.startsWith('image/')) {
168
+ const file = item.getAsFile();
169
+ if (file) {
170
+ files.push(file);
23
171
  }
24
172
  }
25
173
  }
174
+ if (files.length) {
175
+ void addFiles(files);
176
+ }
26
177
  };
27
178
  document.addEventListener('paste', handlePaste);
28
179
  return () => {
29
180
  document.removeEventListener('paste', handlePaste);
30
181
  };
31
- }, [value, enablePaste]);
32
- const handleDragOver = (e) => {
182
+ }, [enablePaste, value, beforeUpload, immediateUpload, action, data, headers, withCredentials, onChange, onUploadChange, maxImages]);
183
+ const handleDrop = (e) => {
33
184
  if (!enableDragDrop)
34
185
  return;
35
186
  e.preventDefault();
36
187
  e.stopPropagation();
37
- setIsDragging(true);
188
+ setIsDragging(false);
189
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
190
+ void addFiles(Array.from(e.dataTransfer.files));
191
+ }
38
192
  };
39
- const handleDragLeave = (e) => {
193
+ const handleDragOver = (e) => {
40
194
  if (!enableDragDrop)
41
195
  return;
42
196
  e.preventDefault();
43
197
  e.stopPropagation();
44
- setIsDragging(false);
198
+ setIsDragging(true);
45
199
  };
46
- const handleDrop = (e) => {
200
+ const handleDragLeave = (e) => {
47
201
  if (!enableDragDrop)
48
202
  return;
49
203
  e.preventDefault();
50
204
  e.stopPropagation();
51
205
  setIsDragging(false);
52
- if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
53
- const fileList = Array.from(e.dataTransfer.files);
54
- fileList.forEach((file) => {
55
- if (file.type.match('image.*')) {
56
- convertToBase64(file);
57
- }
58
- });
59
- }
60
206
  };
61
207
  const handleClick = () => {
62
208
  if (!enableFileSelect)
@@ -67,89 +213,31 @@ export const ImageUploader = (props) => {
67
213
  if (!enableFileSelect)
68
214
  return;
69
215
  if (e.target.files && e.target.files.length > 0) {
70
- const fileList = Array.from(e.target.files);
71
- fileList.forEach((file) => {
72
- if (file.type.match('image.*')) {
73
- convertToBase64(file);
74
- }
75
- });
216
+ void addFiles(Array.from(e.target.files));
76
217
  }
77
218
  };
78
219
  const handlePasteButton = () => {
79
220
  if (!enablePaste)
80
221
  return;
81
222
  navigator.clipboard.read().then((clipboardItems) => {
223
+ const files = [];
82
224
  for (const clipboardItem of clipboardItems) {
83
225
  for (const type of clipboardItem.types) {
84
226
  if (type.startsWith('image/')) {
85
- clipboardItem.getType(type).then((blob) => {
86
- convertToBase64(blob);
87
- });
227
+ files.push(clipboardItem.getType(type));
88
228
  }
89
229
  }
90
230
  }
231
+ return Promise.all(files);
232
+ }).then((files) => {
233
+ if (files.length) {
234
+ void addFiles(files);
235
+ }
91
236
  }).catch(error => {
92
237
  console.error('读取剪贴板失败:', error);
93
238
  alert('读取剪贴板失败。请尝试使用 Ctrl+V 粘贴或手动上传图片。');
94
239
  });
95
240
  };
96
- const convertToBase64 = (file) => {
97
- if (value.length >= maxImages) {
98
- alert(`最多允许上传 ${maxImages} 张图片。`);
99
- return;
100
- }
101
- compressImage(file).then(compressedFile => {
102
- const reader = new FileReader();
103
- reader.onloadend = () => {
104
- const base64String = reader.result;
105
- if (!value.includes(base64String)) {
106
- onChange?.([...value, base64String]);
107
- }
108
- };
109
- reader.readAsDataURL(compressedFile);
110
- });
111
- };
112
- const compressImage = (file) => {
113
- return new Promise((resolve) => {
114
- const originalSize = file.size / 1024;
115
- const img = new Image();
116
- img.src = URL.createObjectURL(file);
117
- img.onload = () => {
118
- URL.revokeObjectURL(img.src);
119
- const canvas = document.createElement('canvas');
120
- let width = img.width;
121
- let height = img.height;
122
- // 计算压缩比例,保持宽高比
123
- const maxWidth = 1200;
124
- const maxHeight = 1200;
125
- if (width > maxWidth || height > maxHeight) {
126
- const ratio = Math.min(maxWidth / width, maxHeight / height);
127
- width = width * ratio;
128
- height = height * ratio;
129
- }
130
- canvas.width = width;
131
- canvas.height = height;
132
- const ctx = canvas.getContext('2d');
133
- if (!ctx) {
134
- resolve(file);
135
- return;
136
- }
137
- ctx.drawImage(img, 0, 0, width, height);
138
- // 转换为blob,压缩质量为0.8
139
- canvas.toBlob((blob) => {
140
- if (!blob) {
141
- resolve(file);
142
- return;
143
- }
144
- // 获取压缩后的图片大小
145
- const compressedSize = blob.size / 1024;
146
- console.log(`原始图片: ${originalSize.toFixed(2)} KB => ${compressedSize.toFixed(2)} KB`);
147
- console.log(`压缩到了: ${((compressedSize / originalSize) * 100).toFixed(2)}%`);
148
- resolve(blob);
149
- }, 'image/jpeg', 0.8);
150
- };
151
- });
152
- };
153
241
  const handleRemoveImage = (index) => {
154
242
  Modal.confirm({
155
243
  title: '确认删除',
@@ -176,7 +264,7 @@ export const ImageUploader = (props) => {
176
264
  height: 'auto',
177
265
  objectFit: 'contain',
178
266
  } })), children: imageNode }) }, index));
179
- }), value.length < maxImages && (_jsxs(Space, { style: { width: '100%' }, children: [enableFileSelect && (_jsxs("div", { className: `border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${isDragging && enableDragDrop ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'}`, onDragOver: enableDragDrop ? handleDragOver : undefined, onDragLeave: enableDragDrop ? handleDragLeave : undefined, onDrop: enableDragDrop ? handleDrop : undefined, onClick: handleClick, style: { width: '50px', height: '50px' }, children: [_jsx(PlusOutlined, { style: { fontSize: '24px', color: '#8c8c8c' } }), _jsx(Typography.Text, { type: 'secondary', className: 'm-2', children: "\u4E0A\u4F20" })] })), enablePaste && (_jsxs("div", { style: { width: '50px', height: '50px' }, className: `border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'}`, onClick: handlePasteButton, children: [_jsx(PlusOutlined, { style: { fontSize: '24px', color: '#8c8c8c' } }), _jsx(Typography.Text, { type: 'secondary', className: 'm-2', children: "\u7C98\u8D34" })] }))] }))] }), enableFileSelect && (_jsx("input", { type: 'file', ref: fileInputRef, onChange: handleFileChange, accept: 'image/*', multiple: true, style: { display: 'none' } })), (enablePasteTip && enablePaste) && (_jsx(Alert, { type: 'info', showIcon: true, title: _jsxs(Typography.Text, { type: 'secondary', children: ["\u63D0\u793A\uFF1A\u60A8\u4E5F\u53EF\u4EE5\u5728\u4EFB\u610F\u4F4D\u7F6E\u6309\u4E0B ", _jsx("kbd", { style: {
267
+ }), value.length < maxImages && (_jsxs(Space, { style: { width: '100%' }, children: [enableFileSelect && (_jsxs("div", { className: `border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${isDragging && enableDragDrop ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'}`, onDrop: enableDragDrop ? handleDrop : undefined, onClick: handleClick, onDragOver: enableDragDrop ? handleDragOver : undefined, onDragLeave: enableDragDrop ? handleDragLeave : undefined, style: { width: '50px', height: '50px' }, children: [_jsx(PlusOutlined, { style: { fontSize: '24px', color: '#8c8c8c' } }), _jsx(Typography.Text, { type: 'secondary', className: 'm-2', children: "\u4E0A\u4F20" })] })), enablePaste && (_jsxs("div", { style: { width: '50px', height: '50px' }, className: `border-2 border-dashed rounded flex flex-col items-center justify-center cursor-pointer transition-colors ${isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-blue-500'}`, onClick: handlePasteButton, children: [_jsx(PlusOutlined, { style: { fontSize: '24px', color: '#8c8c8c' } }), _jsx(Typography.Text, { type: 'secondary', className: 'm-2', children: "\u7C98\u8D34" })] }))] }))] }), enableFileSelect && (_jsx("input", { type: 'file', ref: fileInputRef, onChange: handleFileChange, accept: 'image/*', multiple: true, style: { display: 'none' } })), (enablePasteTip && enablePaste) && (_jsx(Alert, { type: 'info', showIcon: true, title: _jsxs(Typography.Text, { type: 'secondary', children: ["\u63D0\u793A\uFF1A\u60A8\u4E5F\u53EF\u4EE5\u5728\u4EFB\u610F\u4F4D\u7F6E\u6309\u4E0B ", _jsx("kbd", { style: {
180
268
  padding: '0.1rem 0.4rem',
181
269
  background: '#f5f5f5',
182
270
  border: '1px solid #d9d9d9',
@@ -1,6 +1,6 @@
1
1
  import type { PropsWithChildren, ReactNode } from 'react';
2
2
  interface PageBaseProps extends PropsWithChildren {
3
- title: ReactNode;
3
+ title?: ReactNode;
4
4
  }
5
5
  export declare const PageBase: (props: PageBaseProps) => import("react/jsx-runtime").JSX.Element;
6
6
  export {};
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface PanelPageProps {
3
+ children: ReactNode;
4
+ description?: ReactNode;
5
+ eyebrow: string;
6
+ title: string;
7
+ }
8
+ export declare const PanelPage: (props: PanelPageProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Typography } from 'antd';
4
+ export const PanelPage = (props) => {
5
+ const { children, description, eyebrow, title } = props;
6
+ return (_jsxs("div", { className: 'relative min-h-[calc(100vh-100px)] overflow-hidden bg-[linear-gradient(180deg,_#f8fbff_0%,_#eef4ff_52%,_#f8fafc_100%)] dark:bg-[linear-gradient(180deg,_#0f172a_0%,_#111827_55%,_#0f172a_100%)]', children: [_jsx("div", { className: 'absolute inset-0 bg-[linear-gradient(rgba(148,163,184,0.1)_1px,transparent_1px),linear-gradient(90deg,rgba(148,163,184,0.1)_1px,transparent_1px)] bg-[size:32px_32px] opacity-40 dark:opacity-10' }), _jsx("div", { className: 'relative mx-auto flex min-h-[calc(100vh-100px)] max-w-6xl items-center justify-center px-4 py-8 sm:px-6 lg:px-8', children: _jsxs("div", { className: 'w-full max-w-xl overflow-hidden rounded-lg border border-slate-200/80 bg-white/80 shadow-sm backdrop-blur-sm dark:border-white/10 dark:bg-white/5', children: [_jsxs("div", { className: 'border-b border-slate-200/70 px-6 py-6 dark:border-white/10 sm:px-8', children: [_jsx(Typography.Text, { className: 'text-xs font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400', children: eyebrow }), _jsx(Typography.Title, { level: 3, className: '!mb-0 !mt-4 !text-3xl !font-semibold !text-slate-950 dark:!text-slate-50', children: title }), description ? (_jsx("div", { className: 'mt-2 text-sm leading-6 text-slate-500 dark:text-slate-300', children: description })) : null] }), _jsx("div", { className: 'px-4 py-4 sm:px-6 sm:py-6', children: children })] }) })] }));
7
+ };
@@ -4,3 +4,4 @@ export * from './TabsPro';
4
4
  export * from './DragSort';
5
5
  export * from './PageBase';
6
6
  export * from './SideMenu';
7
+ export * from './PanelPage';
@@ -4,3 +4,4 @@ export * from './TabsPro';
4
4
  export * from './DragSort';
5
5
  export * from './PageBase';
6
6
  export * from './SideMenu';
7
+ export * from './PanelPage';
package/dist/web.css CHANGED
@@ -38,12 +38,21 @@
38
38
  margin-left: auto;
39
39
  margin-right: auto
40
40
  }
41
+ .\!mb-0 {
42
+ margin-bottom: 0px !important
43
+ }
44
+ .\!mt-4 {
45
+ margin-top: 1rem !important
46
+ }
41
47
  .mb-6 {
42
48
  margin-bottom: 1.5rem
43
49
  }
44
50
  .ml-2 {
45
51
  margin-left: 0.5rem
46
52
  }
53
+ .mt-2 {
54
+ margin-top: 0.5rem
55
+ }
47
56
  .mt-auto {
48
57
  margin-top: auto
49
58
  }
@@ -68,6 +77,9 @@
68
77
  .h-screen {
69
78
  height: 100vh
70
79
  }
80
+ .min-h-\[calc\(100vh-100px\)\] {
81
+ min-height: calc(100vh - 100px)
82
+ }
71
83
  .w-16 {
72
84
  width: 4rem
73
85
  }
@@ -83,9 +95,15 @@
83
95
  .w-px {
84
96
  width: 1px
85
97
  }
98
+ .max-w-6xl {
99
+ max-width: 72rem
100
+ }
86
101
  .max-w-7xl {
87
102
  max-width: 80rem
88
103
  }
104
+ .max-w-xl {
105
+ max-width: 36rem
106
+ }
89
107
  .flex-1 {
90
108
  flex: 1 1 0%
91
109
  }
@@ -148,12 +166,18 @@
148
166
  .rounded-full {
149
167
  border-radius: 9999px
150
168
  }
169
+ .rounded-lg {
170
+ border-radius: 0.5rem
171
+ }
151
172
  .border {
152
173
  border-width: 1px
153
174
  }
154
175
  .border-2 {
155
176
  border-width: 2px
156
177
  }
178
+ .border-b {
179
+ border-bottom-width: 1px
180
+ }
157
181
  .border-t {
158
182
  border-top-width: 1px
159
183
  }
@@ -175,6 +199,12 @@
175
199
  --tw-border-opacity: 1;
176
200
  border-color: rgb(209 213 219 / var(--tw-border-opacity, 1))
177
201
  }
202
+ .border-slate-200\/70 {
203
+ border-color: rgb(226 232 240 / 0.7)
204
+ }
205
+ .border-slate-200\/80 {
206
+ border-color: rgb(226 232 240 / 0.8)
207
+ }
178
208
  .\!bg-blue-500 {
179
209
  --tw-bg-opacity: 1 !important;
180
210
  background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)) !important
@@ -198,6 +228,18 @@
198
228
  --tw-bg-opacity: 1;
199
229
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1))
200
230
  }
231
+ .bg-white\/80 {
232
+ background-color: rgb(255 255 255 / 0.8)
233
+ }
234
+ .bg-\[linear-gradient\(180deg\2c _\#f8fbff_0\%\2c _\#eef4ff_52\%\2c _\#f8fafc_100\%\)\] {
235
+ background-image: linear-gradient(180deg, #f8fbff 0%, #eef4ff 52%, #f8fafc 100%)
236
+ }
237
+ .bg-\[linear-gradient\(rgba\(148\2c 163\2c 184\2c 0\.1\)_1px\2c transparent_1px\)\2c linear-gradient\(90deg\2c rgba\(148\2c 163\2c 184\2c 0\.1\)_1px\2c transparent_1px\)\] {
238
+ background-image: linear-gradient(rgba(148,163,184,0.1) 1px,transparent 1px),linear-gradient(90deg,rgba(148,163,184,0.1) 1px,transparent 1px)
239
+ }
240
+ .bg-\[size\:32px_32px\] {
241
+ background-size: 32px 32px
242
+ }
201
243
  .object-contain {
202
244
  -o-object-fit: contain;
203
245
  object-fit: contain
@@ -212,16 +254,36 @@
212
254
  padding-left: 1rem;
213
255
  padding-right: 1rem
214
256
  }
257
+ .px-6 {
258
+ padding-left: 1.5rem;
259
+ padding-right: 1.5rem
260
+ }
215
261
  .py-2 {
216
262
  padding-top: 0.5rem;
217
263
  padding-bottom: 0.5rem
218
264
  }
265
+ .py-4 {
266
+ padding-top: 1rem;
267
+ padding-bottom: 1rem
268
+ }
269
+ .py-6 {
270
+ padding-top: 1.5rem;
271
+ padding-bottom: 1.5rem
272
+ }
273
+ .py-8 {
274
+ padding-top: 2rem;
275
+ padding-bottom: 2rem
276
+ }
219
277
  .pr-4 {
220
278
  padding-right: 1rem
221
279
  }
222
280
  .text-center {
223
281
  text-align: center
224
282
  }
283
+ .\!text-3xl {
284
+ font-size: 1.875rem !important;
285
+ line-height: 2.25rem !important
286
+ }
225
287
  .text-2xl {
226
288
  font-size: 1.5rem;
227
289
  line-height: 2rem
@@ -246,15 +308,34 @@
246
308
  font-size: 0.75rem;
247
309
  line-height: 1rem
248
310
  }
311
+ .\!font-semibold {
312
+ font-weight: 600 !important
313
+ }
249
314
  .font-bold {
250
315
  font-weight: 700
251
316
  }
252
317
  .font-medium {
253
318
  font-weight: 500
254
319
  }
320
+ .font-semibold {
321
+ font-weight: 600
322
+ }
323
+ .uppercase {
324
+ text-transform: uppercase
325
+ }
326
+ .leading-6 {
327
+ line-height: 1.5rem
328
+ }
255
329
  .leading-none {
256
330
  line-height: 1
257
331
  }
332
+ .tracking-\[0\.3em\] {
333
+ letter-spacing: 0.3em
334
+ }
335
+ .\!text-slate-950 {
336
+ --tw-text-opacity: 1 !important;
337
+ color: rgb(2 6 23 / var(--tw-text-opacity, 1)) !important
338
+ }
258
339
  .\!text-white {
259
340
  --tw-text-opacity: 1 !important;
260
341
  color: rgb(255 255 255 / var(--tw-text-opacity, 1)) !important
@@ -271,6 +352,10 @@
271
352
  --tw-text-opacity: 1;
272
353
  color: rgb(17 24 39 / var(--tw-text-opacity, 1))
273
354
  }
355
+ .text-slate-500 {
356
+ --tw-text-opacity: 1;
357
+ color: rgb(100 116 139 / var(--tw-text-opacity, 1))
358
+ }
274
359
  .text-white {
275
360
  --tw-text-opacity: 1;
276
361
  color: rgb(255 255 255 / var(--tw-text-opacity, 1))
@@ -284,6 +369,14 @@
284
369
  .opacity-30 {
285
370
  opacity: 0.3
286
371
  }
372
+ .opacity-40 {
373
+ opacity: 0.4
374
+ }
375
+ .shadow-sm {
376
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
377
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
378
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)
379
+ }
287
380
  .outline {
288
381
  outline-style: solid
289
382
  }
@@ -307,6 +400,10 @@
307
400
  .filter {
308
401
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)
309
402
  }
403
+ .backdrop-blur-sm {
404
+ --tw-backdrop-blur: blur(4px);
405
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)
406
+ }
310
407
  .transition {
311
408
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
312
409
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -361,3 +458,47 @@
361
458
  .focus\:ring-offset-2:focus {
362
459
  --tw-ring-offset-width: 2px
363
460
  }
461
+ .dark\:border-white\/10:is(.dark *) {
462
+ border-color: rgb(255 255 255 / 0.1)
463
+ }
464
+ .dark\:bg-white\/5:is(.dark *) {
465
+ background-color: rgb(255 255 255 / 0.05)
466
+ }
467
+ .dark\:bg-\[linear-gradient\(180deg\2c _\#0f172a_0\%\2c _\#111827_55\%\2c _\#0f172a_100\%\)\]:is(.dark *) {
468
+ background-image: linear-gradient(180deg, #0f172a 0%, #111827 55%, #0f172a 100%)
469
+ }
470
+ .dark\:\!text-slate-50:is(.dark *) {
471
+ --tw-text-opacity: 1 !important;
472
+ color: rgb(248 250 252 / var(--tw-text-opacity, 1)) !important
473
+ }
474
+ .dark\:text-slate-300:is(.dark *) {
475
+ --tw-text-opacity: 1;
476
+ color: rgb(203 213 225 / var(--tw-text-opacity, 1))
477
+ }
478
+ .dark\:text-slate-400:is(.dark *) {
479
+ --tw-text-opacity: 1;
480
+ color: rgb(148 163 184 / var(--tw-text-opacity, 1))
481
+ }
482
+ .dark\:opacity-10:is(.dark *) {
483
+ opacity: 0.1
484
+ }
485
+ @media (min-width: 640px) {
486
+ .sm\:px-6 {
487
+ padding-left: 1.5rem;
488
+ padding-right: 1.5rem
489
+ }
490
+ .sm\:px-8 {
491
+ padding-left: 2rem;
492
+ padding-right: 2rem
493
+ }
494
+ .sm\:py-6 {
495
+ padding-top: 1.5rem;
496
+ padding-bottom: 1.5rem
497
+ }
498
+ }
499
+ @media (min-width: 1024px) {
500
+ .lg\:px-8 {
501
+ padding-left: 2rem;
502
+ padding-right: 2rem
503
+ }
504
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/uis",
3
- "version": "0.3.32",
3
+ "version": "0.3.37",
4
4
  "description": "description",
5
5
  "author": "wzy",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  "**/*.css"
10
10
  ],
11
11
  "scripts": {
12
- "build": "rm -rf dist && tsc && rsync -a --include='*/' --include='*.css' --exclude='*' src/ dist/ && postcss ./src/web.css -o ./dist/web.css"
12
+ "build": "rm -rf dist && tsc && rsync -a --include='*/' --include='*.css' --exclude='*' src/ dist/ && postcss ./src/web.css -o ./dist/web.css",
13
+ "typecheck": "tsc --noEmit"
13
14
  },
14
15
  "files": [
15
16
  "dist"
@@ -35,8 +36,8 @@
35
36
  "@ant-design/pro-form": "^2.32.0",
36
37
  "@ant-design/pro-provider": "^2.16.2",
37
38
  "@hello-pangea/dnd": "^18.0.1",
38
- "@wzyjs/hooks": "0.3.32",
39
- "@wzyjs/utils": "0.3.32",
39
+ "@wzyjs/hooks": "0.3.37",
40
+ "@wzyjs/utils": "0.3.37",
40
41
  "@xyflow/react": "^12.5.3",
41
42
  "echarts": "^5.4.3",
42
43
  "echarts-for-react": "^3.0.2",
@@ -69,7 +70,7 @@
69
70
  "postcss-cli": "^11.0.1",
70
71
  "tailwindcss": "^3.4.18"
71
72
  },
72
- "gitHead": "260d8290106ef447ec42375d4656af2e89675b15",
73
+ "gitHead": "66fc9b56ff49d9a725189dbd3360d6987cb9d604",
73
74
  "publishConfig": {
74
75
  "access": "public"
75
76
  }