rht
This commit is contained in:
parent
6985ee863f
commit
0dad0e18ec
|
@ -27,7 +27,7 @@
|
||||||
"@nestjs/jwt": "^10.2.0",
|
"@nestjs/jwt": "^10.2.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/platform-socket.io": "^10.3.10",
|
"@nestjs/platform-socket.io": "^10.3.10",
|
||||||
"@nestjs/schedule": "^4.1.0",
|
"@nestjs/schedule": "^4.1.2",
|
||||||
"@nestjs/websockets": "^10.3.10",
|
"@nestjs/websockets": "^10.3.10",
|
||||||
"@nice/common": "workspace:*",
|
"@nice/common": "workspace:*",
|
||||||
"@nice/tus": "workspace:*",
|
"@nice/tus": "workspace:*",
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { customAlphabet } from 'nanoid-cjs';
|
||||||
import { db } from '@nice/common';
|
import { db } from '@nice/common';
|
||||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||||
import { ResourceService } from '@server/models/resource/resource.service';
|
import { ResourceService } from '@server/models/resource/resource.service';
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
|
||||||
export interface ShareCode {
|
export interface ShareCode {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -44,7 +47,7 @@ export class ShareCodeService {
|
||||||
const resource = await this.resourceService.findUnique({
|
const resource = await this.resourceService.findUnique({
|
||||||
where: { fileId },
|
where: { fileId },
|
||||||
});
|
});
|
||||||
console.log('完整 fileId:', fileId); // 确保与前端一致
|
this.logger.log('完整 fileId:', fileId); // 确保与前端一致
|
||||||
|
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
throw new NotFoundException('文件不存在');
|
throw new NotFoundException('文件不存在');
|
||||||
|
@ -53,7 +56,7 @@ export class ShareCodeService {
|
||||||
// 生成分享码
|
// 生成分享码
|
||||||
const code = this.generateCode();
|
const code = this.generateCode();
|
||||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期
|
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期
|
||||||
|
//const expiresAt = new Date(Date.now() + 10 * 1000); // 24小时后过期
|
||||||
// 查找是否已有分享码记录
|
// 查找是否已有分享码记录
|
||||||
const existingShareCode = await db.shareCode.findUnique({
|
const existingShareCode = await db.shareCode.findUnique({
|
||||||
where: { fileId },
|
where: { fileId },
|
||||||
|
@ -96,7 +99,7 @@ export class ShareCodeService {
|
||||||
|
|
||||||
async validateAndUseCode(code: string): Promise<ShareCode | null> {
|
async validateAndUseCode(code: string): Promise<ShareCode | null> {
|
||||||
try {
|
try {
|
||||||
console.log(`尝试验证分享码: ${code}`);
|
this.logger.log(`尝试验证分享码: ${code}`);
|
||||||
|
|
||||||
// 查找有效的分享码
|
// 查找有效的分享码
|
||||||
const shareCode = await db.shareCode.findFirst({
|
const shareCode = await db.shareCode.findFirst({
|
||||||
|
@ -107,10 +110,10 @@ export class ShareCodeService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('查询结果:', shareCode);
|
this.logger.log('查询结果:', shareCode);
|
||||||
|
|
||||||
if (!shareCode) {
|
if (!shareCode) {
|
||||||
console.log('分享码无效或已过期');
|
this.logger.log('分享码无效或已过期');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,21 +135,74 @@ export class ShareCodeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每天清理过期的分享码
|
// 每天清理过期的分享码
|
||||||
|
//@Cron('*/30 * * * * *')
|
||||||
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||||
async cleanupExpiredShareCodes() {
|
async cleanupExpiredShareCodes() {
|
||||||
try {
|
try {
|
||||||
const result = await db.shareCode.deleteMany({
|
const shareCodes = await db.shareCode.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }],
|
OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.logger.log('需要清理的分享码:', shareCodes);
|
||||||
|
shareCodes.forEach(code => {
|
||||||
|
this.cleanupUploadFolder(code.fileId);
|
||||||
|
})
|
||||||
|
const result = await db.shareCode.deleteMany({
|
||||||
|
where: {
|
||||||
|
fileId: {
|
||||||
|
in: shareCodes.map(code => code.fileId)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const deleteResource = await this.resourceService.deleteMany({
|
||||||
this.logger.log(`Cleaned up ${result.count} expired share codes`);
|
where: {
|
||||||
|
fileId: {
|
||||||
|
in: shareCodes.map(code => code.fileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.logger.log(`Cleaned up ${result.count} ${deleteResource.count} expired share codes`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to cleanup expired share codes', error);
|
this.logger.error('Failed to cleanup expired share codes', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cleanupUploadFolder(file?: string) {
|
||||||
|
//const uploadDir = path.join(__dirname, '../../../uploads');
|
||||||
|
const uploadDir = path.join('/data/uploads/', file || '');
|
||||||
|
this.logger.log('uploadDir:', uploadDir);
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(uploadDir)) {
|
||||||
|
this.logger.warn(`Upload directory does not exist: ${uploadDir}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 递归删除文件夹及其内容
|
||||||
|
this.deleteFolderRecursive(uploadDir);
|
||||||
|
this.logger.log(`Cleaned up upload folder: ${uploadDir}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('读取上传目录失败:', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private deleteFolderRecursive(dirPath: string) {
|
||||||
|
if (fs.existsSync(dirPath)) {
|
||||||
|
fs.readdirSync(dirPath).forEach((file) => {
|
||||||
|
const filePath = path.join(dirPath, file);
|
||||||
|
if (fs.statSync(filePath).isDirectory()) {
|
||||||
|
// 递归删除子目录
|
||||||
|
this.deleteFolderRecursive(filePath);
|
||||||
|
} else {
|
||||||
|
// 删除文件
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
this.logger.log(`Deleted file: ${filePath}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 删除空文件夹
|
||||||
|
fs.rmdirSync(dirPath);
|
||||||
|
this.logger.log(`Deleted folder: ${dirPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 获取分享码信息
|
// 获取分享码信息
|
||||||
async getShareCodeInfo(code: string): Promise<ShareCode | null> {
|
async getShareCodeInfo(code: string): Promise<ShareCode | null> {
|
||||||
try {
|
try {
|
||||||
|
@ -192,10 +248,10 @@ export class ShareCodeService {
|
||||||
|
|
||||||
// 根据分享码获取文件
|
// 根据分享码获取文件
|
||||||
async getFileByShareCode(code: string) {
|
async getFileByShareCode(code: string) {
|
||||||
console.log('收到验证分享码请求,code:', code);
|
this.logger.log('收到验证分享码请求,code:', code);
|
||||||
|
|
||||||
const shareCode = await this.validateAndUseCode(code);
|
const shareCode = await this.validateAndUseCode(code);
|
||||||
console.log('验证分享码结果:', shareCode);
|
this.logger.log('验证分享码结果:', shareCode);
|
||||||
|
|
||||||
if (!shareCode) {
|
if (!shareCode) {
|
||||||
throw new NotFoundException('分享码无效或已过期');
|
throw new NotFoundException('分享码无效或已过期');
|
||||||
|
@ -205,8 +261,9 @@ export class ShareCodeService {
|
||||||
const resource = await this.resourceService.findUnique({
|
const resource = await this.resourceService.findUnique({
|
||||||
where: { fileId: shareCode.fileId },
|
where: { fileId: shareCode.fileId },
|
||||||
});
|
});
|
||||||
console.log('获取到的资源信息:', resource);
|
this.logger.log('获取到的资源信息:', resource);
|
||||||
const { filename } = resource.meta as any as ResourceMeta
|
const { filename } = resource.meta as any as ResourceMeta
|
||||||
|
const fileUrl = resource?.url
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
throw new NotFoundException('文件不存在');
|
throw new NotFoundException('文件不存在');
|
||||||
}
|
}
|
||||||
|
@ -216,22 +273,23 @@ export class ShareCodeService {
|
||||||
fileId: shareCode.fileId,
|
fileId: shareCode.fileId,
|
||||||
fileName: filename || 'downloaded_file',
|
fileName: filename || 'downloaded_file',
|
||||||
code: shareCode.code,
|
code: shareCode.code,
|
||||||
expiresAt: shareCode.expiresAt
|
expiresAt: shareCode.expiresAt,
|
||||||
|
url: fileUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('返回给前端的数据:', response); // 添加日志
|
this.logger.log('返回给前端的数据:', response); // 添加日志
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据文件ID生成分享码
|
// 根据文件ID生成分享码
|
||||||
async generateShareCodeByFileId(fileId: string) {
|
async generateShareCodeByFileId(fileId: string) {
|
||||||
try {
|
try {
|
||||||
console.log('收到生成分享码请求,fileId:', fileId);
|
this.logger.log('收到生成分享码请求,fileId:', fileId);
|
||||||
const result = await this.generateShareCode(fileId);
|
const result = await this.generateShareCode(fileId);
|
||||||
console.log('生成分享码结果:', result);
|
this.logger.log('生成分享码结果:', result);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成分享码错误:', error);
|
this.logger.error('生成分享码错误:', error);
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,233 +1,95 @@
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
|
||||||
import { ShareCodeGenerator } from "../sharecode/sharecodegenerator";
|
import { ShareCodeGenerator } from "../sharecode/sharecodegenerator";
|
||||||
import { ShareCodeValidator } from "../sharecode/sharecodevalidator";
|
import { ShareCodeValidator } from "../sharecode/sharecodevalidator";
|
||||||
import { useState, useRef, useCallback, useEffect } from "react";
|
import { message, Tabs, Form, Spin } from "antd";
|
||||||
import { message, Progress, Button, Tabs, DatePicker, Form } from "antd";
|
|
||||||
import { UploadOutlined, DeleteOutlined, InboxOutlined } from "@ant-design/icons";
|
|
||||||
import { env } from '../../../env'
|
import { env } from '../../../env'
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||||
|
import { useState } from "react";
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
export default function DeptSettingPage() {
|
export default function DeptSettingPage() {
|
||||||
const [uploadedFileId, setUploadedFileId] = useState<string>('');
|
|
||||||
const [uploadedFileName, setUploadedFileName] = useState<string>('');
|
|
||||||
const [fileNameMap, setFileNameMap] = useState<Record<string, string>>({});
|
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<{ id: string, name: string }[]>([]);
|
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
const [expireTime, setExpireTime] = useState<Date | null>(null);
|
|
||||||
const dropRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [currentFile, setCurrentFile] = useState<string[]>([])
|
|
||||||
const uploadFileId = Form.useWatch(["file"], form)?.[0]
|
const uploadFileId = Form.useWatch(["file"], form)?.[0]
|
||||||
// 使用您的 useTusUpload hook
|
const [isGetingFileId, setIsGetingFileId] = useState(false);
|
||||||
const { uploadProgress, isUploading, uploadError, handleFileUpload } = useTusUpload();
|
|
||||||
|
|
||||||
// 清除已上传文件
|
|
||||||
const handleClearFile = () => {
|
|
||||||
setUploadedFileId('');
|
|
||||||
setUploadedFileName('');
|
|
||||||
setUploadedFiles([]);
|
|
||||||
setFileNameMap({});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理文件上传
|
|
||||||
const handleFileSelect = async (file: File) => {
|
|
||||||
// 限制:如果已有上传文件,则提示用户
|
|
||||||
if (uploadedFiles.length > 0) {
|
|
||||||
message.warning('只能上传一个文件,请先删除已上传的文件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileKey = `file-${Date.now()}`; // 生成唯一的文件标识
|
|
||||||
|
|
||||||
handleFileUpload(
|
|
||||||
file,
|
|
||||||
async (result) => {
|
|
||||||
setUploadedFileId(result.fileId);
|
|
||||||
setUploadedFileName(result.fileName);
|
|
||||||
|
|
||||||
// 添加到已上传文件列表
|
|
||||||
setUploadedFiles([{ id: result.fileId, name: file.name }]);
|
|
||||||
|
|
||||||
// 在前端保存文件名映射(用于当前会话)
|
|
||||||
setFileNameMap({
|
|
||||||
[result.fileId]: file.name
|
|
||||||
});
|
|
||||||
|
|
||||||
// 上传成功后保存原始文件名到数据库
|
|
||||||
try {
|
|
||||||
console.log('正在保存文件名到数据库:', result.fileName, '对应文件ID:', result.fileId);
|
|
||||||
|
|
||||||
const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/filename`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
fileId: result.fileId,
|
|
||||||
fileName: file.name
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const responseText = await response.text();
|
|
||||||
console.log('保存文件名响应:', response.status, responseText);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('保存文件名失败:', responseText);
|
|
||||||
message.warning('文件名保存失败,下载时可能无法显示原始文件名');
|
|
||||||
} else {
|
|
||||||
console.log('文件名保存成功:', file.name);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('保存文件名请求失败:', error);
|
|
||||||
message.warning('文件名保存失败,下载时可能无法显示原始文件名');
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('文件上传成功');
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
message.error('上传失败:' + error.message);
|
|
||||||
},
|
|
||||||
fileKey
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理多个文件上传 - 已移除
|
|
||||||
// const handleFilesUpload = (file: File) => {
|
|
||||||
// handleFileSelect(file);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 处理文件删除
|
|
||||||
const handleDeleteFile = async (fileId: string) => {
|
|
||||||
try {
|
|
||||||
// 可以添加删除文件的API调用
|
|
||||||
// const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/delete/${fileId}`, {
|
|
||||||
// method: 'DELETE'
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!response.ok) {
|
|
||||||
// throw new Error('删除文件失败');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 无论服务器删除是否成功,前端都需要更新状态
|
|
||||||
setUploadedFiles([]);
|
|
||||||
setUploadedFileId('');
|
|
||||||
setUploadedFileName('');
|
|
||||||
setFileNameMap({});
|
|
||||||
|
|
||||||
message.success('文件已删除');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除文件错误:', error);
|
|
||||||
message.error('删除文件失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 拖拽相关处理函数
|
|
||||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(false);
|
|
||||||
|
|
||||||
handleFileSelect(e.dataTransfer.files[0]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
// 处理分享码生成成功
|
// 处理分享码生成成功
|
||||||
const handleShareSuccess = (code: string) => {
|
const handleShareSuccess = (code: string) => {
|
||||||
message.success('分享码生成成功:' + code);
|
message.success('分享码生成成功:' + code);
|
||||||
// 可以在这里添加其他逻辑,比如保存到历史记录
|
// 可以在这里添加其他逻辑,比如保存到历史记录
|
||||||
};
|
}
|
||||||
|
|
||||||
// 处理分享码验证成功
|
// 处理分享码验证成功
|
||||||
const handleValidSuccess = async (fileId: string, fileName: string) => {
|
const handleValidSuccess = async (fileUrl: string, fileName: string) => {
|
||||||
|
setIsGetingFileId(true);
|
||||||
try {
|
try {
|
||||||
// 构建下载URL(包含文件名参数)
|
// 构建下载URL(包含文件名参数)
|
||||||
const downloadUrl = `/upload/download/${fileId}?fileName=${encodeURIComponent(fileName)}`;
|
console.log('文件url:', fileUrl);
|
||||||
const response = await fetch(downloadUrl);
|
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
|
||||||
if (!response.ok) {
|
console.log('下载URL:', downloadUrl);
|
||||||
throw new Error('文件下载失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建下载链接
|
|
||||||
const blob = await response.blob();
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = downloadUrl;
|
||||||
|
|
||||||
// 直接使用传入的 fileName
|
// 直接使用传入的 fileName
|
||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
|
link.target = '_blank'; // 在新标签页中打开
|
||||||
// 触发下载
|
// 触发下载
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
message.success('文件下载开始');
|
message.success('文件下载开始');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载失败:', error);
|
console.error('下载失败:', error);
|
||||||
message.error('文件下载失败');
|
message.error('文件下载失败');
|
||||||
|
} finally {
|
||||||
|
setIsGetingFileId(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
|
<>
|
||||||
<span className="text-2xl py-4">文件分享中心</span>
|
{
|
||||||
<Tabs defaultActiveKey="upload">
|
isGetingFileId ?
|
||||||
<TabPane tab="上传分享" key="upload">
|
(<Spin spinning={isGetingFileId} fullscreen />) :
|
||||||
{/* 文件上传区域 */}
|
(<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
|
||||||
<div style={{ marginBottom: '40px' }}>
|
<span className="text-2xl py-4">文件分享中心</span>
|
||||||
<span className="text-lg block text-zinc-700 py-2">第一步:上传文件</span>
|
<Tabs defaultActiveKey="upload">
|
||||||
{/* 如果没有已上传文件,显示上传区域 */}
|
<TabPane tab="上传分享" key="upload">
|
||||||
<Form form={form}>
|
{/* 文件上传区域 */}
|
||||||
<Form.Item name="file">
|
<div style={{ marginBottom: '40px' }}>
|
||||||
<TusUploader
|
<span className="text-lg block text-zinc-700 py-2">第一步:上传文件</span>
|
||||||
multiple={false}
|
{/* 如果没有已上传文件,显示上传区域 */}
|
||||||
style={"w-full py-4"}
|
<Form form={form}>
|
||||||
></TusUploader>
|
<Form.Item name="file">
|
||||||
</Form.Item>
|
<TusUploader
|
||||||
</Form>
|
multiple={false}
|
||||||
</div>
|
style={"w-full py-4"}
|
||||||
|
></TusUploader>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 生成分享码区域 */}
|
{/* 生成分享码区域 */}
|
||||||
<div style={{ marginBottom: '40px' }}>
|
<div style={{ marginBottom: '40px' }}>
|
||||||
<span className="text-lg block text-zinc-700 py-4">第二步:生成分享码</span>
|
<span className="text-lg block text-zinc-700 py-4">第二步:生成分享码</span>
|
||||||
<ShareCodeGenerator
|
<ShareCodeGenerator
|
||||||
fileId={uploadFileId}
|
fileId={uploadFileId}
|
||||||
onSuccess={handleShareSuccess}
|
onSuccess={handleShareSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
|
{/* 使用分享码区域 */}
|
||||||
|
<TabPane tab="下载文件" key="download">
|
||||||
|
<div>
|
||||||
|
<span className="text-lg block text-zinc-700 py-4">使用分享码下载文件</span>
|
||||||
|
<ShareCodeValidator
|
||||||
|
onValidSuccess={handleValidSuccess}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
{/* 使用分享码区域 */}
|
|
||||||
<TabPane tab="下载文件" key="download">
|
|
||||||
<div>
|
|
||||||
<span className="text-lg block text-zinc-700 py-4">使用分享码下载文件</span>
|
|
||||||
<ShareCodeValidator
|
|
||||||
onValidSuccess={handleValidSuccess}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ interface ShareCodeGeneratorProps {
|
||||||
interface ShareCodeResponse {
|
interface ShareCodeResponse {
|
||||||
code?: string;
|
code?: string;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
fileId,
|
fileId,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
|
@ -39,6 +39,10 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
setCurrentFileId(fileId);
|
setCurrentFileId(fileId);
|
||||||
}, [fileId])
|
}, [fileId])
|
||||||
const generateCode = async () => {
|
const generateCode = async () => {
|
||||||
|
if (!fileId) {
|
||||||
|
message.error('请先上传文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName);
|
console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName);
|
||||||
try {
|
try {
|
||||||
|
@ -48,7 +52,7 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
setIsGenerate(true);
|
setIsGenerate(true);
|
||||||
setExpiresAt(data.expiresAt ? new Date(data.expiresAt) : null);
|
setExpiresAt(data.expiresAt ? new Date(data.expiresAt) : null);
|
||||||
onSuccess?.(data.code);
|
onSuccess?.(data.code);
|
||||||
message.success('分享码生成成功');
|
//message.success('分享码生成成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成分享码错误:', error);
|
console.error('生成分享码错误:', error);
|
||||||
message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
||||||
|
@ -56,6 +60,26 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
return navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件使用
|
||||||
|
const handleCopy = (code) => {
|
||||||
|
copyToClipboard(code)
|
||||||
|
.then(() => console.log('复制成功'))
|
||||||
|
.catch(() => console.error('复制失败'));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
|
<div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
|
||||||
|
@ -96,7 +120,8 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
<Button
|
<Button
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(shareCode);
|
handleCopy(shareCode)
|
||||||
|
//navigator.clipboard.writeText(shareCode);
|
||||||
message.success('分享码已复制');
|
message.success('分享码已复制');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -22,23 +22,23 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
||||||
|
|
||||||
|
|
||||||
const validateCode = useCallback(() => {
|
const validateCode = useCallback(() => {
|
||||||
|
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
message.warning('请输入分享码');
|
message.warning('请输入正确的分享码');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
console.log('验证分享码返回数据:', result);
|
console.log('验证分享码返回数据:', result);
|
||||||
onValidSuccess(result.fileId, result.fileName);
|
onValidSuccess(result.url, result.fileName);
|
||||||
message.success(`验证成功,文件名:${result.fileName}`);
|
message.success(`验证成功,文件名:${result.fileName},请等待下载...`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('验证分享码失败:', error);
|
console.error('验证分享码失败:', error);
|
||||||
message.error('分享码无效或已过期');
|
message.error('分享码无效或已过期');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},[result])
|
},[result,code, onValidSuccess])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
|
|
@ -32,7 +32,7 @@ importers:
|
||||||
specifier: ^10.3.10
|
specifier: ^10.3.10
|
||||||
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.15)(rxjs@7.8.1)
|
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.15)(rxjs@7.8.1)
|
||||||
'@nestjs/schedule':
|
'@nestjs/schedule':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.2
|
||||||
version: 4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)
|
version: 4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)
|
||||||
'@nestjs/websockets':
|
'@nestjs/websockets':
|
||||||
specifier: ^10.3.10
|
specifier: ^10.3.10
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue