components_uploader.tsx 5.0 KB

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