2
0

components_uploader.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import React, { useState } from 'react';
  2. import {
  3. Layout, Menu, Button, Table, Space,
  4. Form, Input, Select, message, Modal,
  5. Card, Spin, Row, Col, Breadcrumb, Avatar,
  6. Dropdown, ConfigProvider, theme, Typography,
  7. Switch, Badge, Image, Upload, Divider, Descriptions,
  8. Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
  9. } from 'antd';
  10. import {
  11. UploadOutlined,
  12. } from '@ant-design/icons';
  13. import { uploadMinIOWithPolicy , uploadOSSWithPolicy} from '@d8d-appcontainer/api';
  14. import { getGlobalConfig } from './utils.ts';
  15. import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
  16. import 'dayjs/locale/zh-cn';
  17. import { OssType } from '../share/types.ts';
  18. import { FileAPI } from './api/index.ts';
  19. // MinIO文件上传组件
  20. export const Uploader = ({
  21. onSuccess,
  22. onError,
  23. onProgress,
  24. maxSize = 10 * 1024 * 1024,
  25. prefix = 'uploads/',
  26. allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'text/plain']
  27. }: {
  28. onSuccess?: (fileUrl: string, fileInfo: any) => void;
  29. onError?: (error: Error) => void;
  30. onProgress?: (percent: number) => void;
  31. maxSize?: number;
  32. prefix?: string;
  33. allowedTypes?: string[];
  34. }) => {
  35. const [uploading, setUploading] = useState(false);
  36. const [progress, setProgress] = useState(0);
  37. // 处理文件上传
  38. const handleUpload = async (options: any) => {
  39. const { file, onSuccess: uploadSuccess, onError: uploadError, onProgress: uploadProgress } = options;
  40. setUploading(true);
  41. setProgress(0);
  42. // 文件大小检查
  43. if (file.size > maxSize) {
  44. message.error(`文件大小不能超过${maxSize / 1024 / 1024}MB`);
  45. uploadError(new Error('文件过大'));
  46. setUploading(false);
  47. return;
  48. }
  49. // 文件类型检查
  50. if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
  51. message.error(`不支持的文件类型: ${file.type}`);
  52. uploadError(new Error('不支持的文件类型'));
  53. setUploading(false);
  54. return;
  55. }
  56. try {
  57. // 1. 获取上传策略
  58. const policyResponse = await FileAPI.getUploadPolicy(file.name, prefix, maxSize);
  59. const policy = policyResponse.data;
  60. if (!policy) {
  61. throw new Error('获取上传策略失败');
  62. }
  63. // 生成随机文件名但保留原始扩展名
  64. const fileExt = file.name.split('.').pop() || '';
  65. const randomName = `${Date.now()}_${Math.random().toString(36).substring(2, 10)}${fileExt ? `.${fileExt}` : ''}`;
  66. // 2. 上传文件到MinIO
  67. const callbacks = {
  68. onProgress: (event: { progress: number }) => {
  69. const percent = Math.round(event.progress);
  70. setProgress(percent);
  71. uploadProgress({ percent });
  72. onProgress?.(percent);
  73. },
  74. onComplete: () => {
  75. setUploading(false);
  76. setProgress(100);
  77. },
  78. onError: (err: Error) => {
  79. setUploading(false);
  80. message.error(`上传失败: ${err.message}`);
  81. uploadError(err);
  82. onError?.(err);
  83. }
  84. };
  85. // 执行上传
  86. const fileUrl = getGlobalConfig('OSS_TYPE') === OssType.MINIO ?
  87. await uploadMinIOWithPolicy(
  88. policy as MinioUploadPolicy,
  89. file,
  90. randomName,
  91. callbacks
  92. ) : await uploadOSSWithPolicy(
  93. policy as OSSUploadPolicy,
  94. file,
  95. randomName,
  96. callbacks
  97. );
  98. // 从URL中提取相对路径
  99. const relativePath = `${policy.prefix}${randomName}`;
  100. // 3. 保存文件信息到文件库
  101. const fileInfo = {
  102. file_name: randomName,
  103. original_filename: file.name,
  104. file_path: relativePath,
  105. file_type: file.type,
  106. file_size: file.size,
  107. tags: '',
  108. description: '',
  109. category_id: undefined
  110. };
  111. const saveResponse = await FileAPI.saveFileInfo(fileInfo);
  112. // 操作成功
  113. uploadSuccess(relativePath);
  114. message.success('文件上传成功');
  115. onSuccess?.(relativePath, saveResponse.data);
  116. } catch (error: any) {
  117. // 上传失败
  118. setUploading(false);
  119. message.error(`上传失败: ${error.message}`);
  120. uploadError(error);
  121. onError?.(error);
  122. }
  123. };
  124. return (
  125. <Upload.Dragger
  126. name="file"
  127. multiple={false}
  128. customRequest={handleUpload}
  129. showUploadList={true}
  130. progress={{
  131. strokeColor: {
  132. '0%': '#108ee9',
  133. '100%': '#87d068',
  134. },
  135. format: (percent) => `${Math.round(percent || 0)}%`,
  136. }}
  137. >
  138. <p className="ant-upload-drag-icon">
  139. <UploadOutlined />
  140. </p>
  141. <p className="ant-upload-text">点击或拖动文件到这里上传</p>
  142. <p className="ant-upload-hint">
  143. 支持单个文件上传,最大{maxSize / 1024 / 1024}MB
  144. </p>
  145. {uploading && (
  146. <div style={{ marginTop: 16 }}>
  147. <Progress percent={progress} />
  148. </div>
  149. )}
  150. </Upload.Dragger>
  151. );
  152. };