This commit is contained in:
Rao 2025-04-09 17:01:21 +08:00
parent 9edce44b32
commit 34dde48aaf
10 changed files with 375 additions and 183 deletions

View File

@ -9,6 +9,7 @@ import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import { BaseService } from '../base/base.service'; import { BaseService } from '../base/base.service';
import { url } from 'inspector';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
export interface ShareCode { export interface ShareCode {
@ -25,12 +26,19 @@ export interface GenerateShareCodeResponse {
code: string; code: string;
expiresAt: Date; expiresAt: Date;
canUseTimes: number; canUseTimes: number;
fileName?: string;
resource: {
id: string;
type: string;
url: string;
meta: ResourceMeta
}
} }
interface ResourceMeta { interface ResourceMeta {
filename: string; filename: string;
filetype: string; filetype: string;
filesize: string; size: string;
} }
@Injectable() @Injectable()
@ -61,7 +69,7 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
if (!resource) { if (!resource) {
throw new NotFoundException('文件不存在'); throw new NotFoundException('文件不存在');
} }
const { filename } = resource.meta as any as ResourceMeta const { filename, filetype, size } = resource.meta as any as ResourceMeta
// 生成分享码 // 生成分享码
const code = this.generateCode(); const code = this.generateCode();
// 查找是否已有分享码记录 // 查找是否已有分享码记录
@ -78,7 +86,7 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
expiresAt, expiresAt,
canUseTimes, canUseTimes,
isUsed: false, isUsed: false,
fileName: filename|| "downloaded_file", fileName: filename || "downloaded_file",
}, },
}); });
} else { } else {
@ -99,6 +107,17 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
code, code,
expiresAt, expiresAt,
canUseTimes, canUseTimes,
fileName: filename || "downloaded_file",
resource: {
id: resource.id,
type: resource.type,
url: resource.url,
meta: {
filename,
filetype,
size,
}
}
}; };
} catch (error) { } catch (error) {
this.logger.error('Failed to generate share code', error); this.logger.error('Failed to generate share code', error);
@ -274,7 +293,7 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
where: { fileId: shareCode.fileId }, where: { fileId: shareCode.fileId },
}); });
this.logger.log('获取到的资源信息:', resource); this.logger.log('获取到的资源信息:', resource);
const { filename } = resource.meta as any as ResourceMeta const { filename,filetype,size } = resource.meta as any as ResourceMeta
const fileUrl = resource?.url const fileUrl = resource?.url
if (!resource) { if (!resource) {
throw new NotFoundException('文件不存在'); throw new NotFoundException('文件不存在');
@ -282,12 +301,19 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
// 直接返回正确的数据结构 // 直接返回正确的数据结构
const response = { const response = {
fileId: shareCode.fileId, id:shareCode.id,
fileName: filename || 'downloaded_file',
code: shareCode.code, code: shareCode.code,
fileName: filename || 'downloaded_file',
expiresAt: shareCode.expiresAt, expiresAt: shareCode.expiresAt,
url: fileUrl,
canUseTimes: shareCode.canUseTimes - 1, canUseTimes: shareCode.canUseTimes - 1,
resource:{
id:resource.id,
type:resource.type,
url:resource.url,
meta:{
filename,filetype,size
}
}
}; };
this.logger.log('返回给前端的数据:', response); // 添加日志 this.logger.log('返回给前端的数据:', response); // 添加日志
@ -316,11 +342,10 @@ export class ShareCodeService extends BaseService<Prisma.ShareCodeDelegate> {
totalPages: number; totalPages: number;
}> { }> {
try { try {
console.log('args:', args.where.OR);
// 使用include直接关联查询Resource // 使用include直接关联查询Resource
const { items, totalPages } = await super.findManyWithPagination({ const { items, totalPages } = await super.findManyWithPagination({
...args, ...args,
select:{ select: {
id: true, id: true,
code: true, code: true,
fileId: true, fileId: true,

View File

@ -3,6 +3,7 @@ import { api } from "@nice/client";
import { createContext, useContext, useState } from "react"; import { createContext, useContext, useState } from "react";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query"; import { getQueryKey } from "@trpc/react-query";
import { ShareCodeResponse } from "../quick-file/quickFileContext";
interface CodeManageContextType { interface CodeManageContextType {
editForm: FormInstance<any>; editForm: FormInstance<any>;
@ -22,21 +23,7 @@ interface CodeManageContextType {
} }
interface ShareCodeWithResource { interface ShareCodeWithResource {
items: { items: ShareCodeResponse[],
id: string;
code: string;
fileName: string;
fileSize: number;
expiresAt: string;
createdAt: string;
canUseTimes: number;
resource: {
id: string;
type: string;
url: string;
meta: any;
}
}[],
totalPages: number totalPages: number
} }

View File

@ -4,29 +4,14 @@ import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import ShareCodeListCard from './ShareCodeListCard'; import ShareCodeListCard from './ShareCodeListCard';
import { ShareCodeResponse } from '../../quick-file/quickFileContext';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
export interface ShareCodeItem {
id: string;
code: string;
expiresAt: string;
fileName: string;
canUseTimes: number;
resource: {
id: string;
type: string;
url: string;
meta: {
size: number;
filename: string;
};
}
}
interface ShareCodeListProps { interface ShareCodeListProps {
data: ShareCodeItem[]; data: ShareCodeResponse[];
loading?: boolean; loading?: boolean;
onEdit?: (id: string,expiresAt:Date,canUseTimes:number,code:string) => void; onEdit?: (id: string,expiresAt:Date,canUseTimes:number,code:string) => void;
onDelete?: (id: string) => void; onDelete?: (id: string) => void;
@ -46,7 +31,7 @@ const ShareCodeList: React.FC<ShareCodeListProps> = ({
loading={loading} loading={loading}
renderItem={(item) => ( renderItem={(item) => (
<List.Item> <List.Item>
<ShareCodeListCard item={item} onEdit={onEdit} onDelete={onDelete} /> <ShareCodeListCard item={item} onEdit={onEdit} onDelete={onDelete} styles='w-[262px]' />
</List.Item> </List.Item>
)} )}
/> />

View File

@ -1,14 +1,21 @@
import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; import { DeleteOutlined, DownloadOutlined, EditOutlined } from "@ant-design/icons";
import { Button, Card, Typography } from "antd"; import { Button, Card, Typography } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ShareCodeItem } from "./ShareCodeList";
import { useEffect } from "react"; import { useEffect } from "react";
import { ShareCodeResponse } from "../../quick-file/quickFileContext";
export default function ShareCodeListCard({ item, onEdit, onDelete }: { item: ShareCodeItem, onEdit: (id: string,expiresAt:Date,canUseTimes:number,code:string) => void, onDelete: (id: string) => void }) { export default function ShareCodeListCard({ item, onEdit, onDelete, styles, onDownload }:
{
item: ShareCodeResponse,
styles?: string,
onDelete: (id: string) => void,
onEdit?: (id: string, expiresAt: Date, canUseTimes: number, code: string) => void,
onDownload?: (id: string) => void
}) {
useEffect(() => { useEffect(() => {
console.log('item:', item); console.log('item:', item);
}, [item]); }, [item]);
return <div> return <div className={`${styles}`}>
<Card <Card
className="shadow-md hover:shadow-lg transition-shadow duration-300 space-x-4" className="shadow-md hover:shadow-lg transition-shadow duration-300 space-x-4"
title={ title={
@ -21,28 +28,38 @@ export default function ShareCodeListCard({ item, onEdit, onDelete }: { item: Sh
} }
hoverable hoverable
actions={[ actions={[
onEdit && (
<Button <Button
type="text" type="text"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => onEdit?.(item.id,dayjs(item.expiresAt).toDate(),item.canUseTimes,item.code)} onClick={() => onEdit?.(item.id, dayjs(item.expiresAt).toDate(), item.canUseTimes, item.code)}
className="text-blue-500 hover:text-blue-700" className="text-blue-500 hover:text-blue-700"
/>, />
),
onDownload && (
<Button
type="text"
icon={<DownloadOutlined />}
onClick={() => onDownload?.(item.code)}
className="text-blue-500 hover:text-blue-700"
/>
),
<Button <Button
type="text" type="text"
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
onClick={() => onDelete?.(item.id)} onClick={() => onDelete?.(item.id)}
className="text-red-500 hover:text-red-700" className="text-red-500 hover:text-red-700"
/> />
]} ].filter(Boolean)}
> >
<div className="space-y-2"> <div className="space-y-2">
<p className="text-gray-700"> <p className="text-gray-700">
<span className="font-medium">:</span> {item.fileName} <span className="font-medium">:</span> {item.fileName}
</p> </p>
<p className="text-gray-700"> <p className="text-gray-700">
<span className="font-medium">:</span> {Math.max(0.01, (item?.resource?.meta?.size / 1024 / 1024)).toFixed(2)} MB <span className="font-medium">:</span> {Math.max(0.01, (Number(item?.resource?.meta?.size) / 1024 / 1024)).toFixed(2)} MB
</p> </p>
<p className="text-gray-700"> <p className="text-gray-700 ">
<span className="font-medium">:</span> {dayjs(item.expiresAt).format('YYYY-MM-DD HH:mm:ss')} <span className="font-medium">:</span> {dayjs(item.expiresAt).format('YYYY-MM-DD HH:mm:ss')}
</p> </p>
<p className="text-gray-700"> <p className="text-gray-700">

View File

@ -5,47 +5,26 @@ import { message, Tabs, Form, Spin, Alert } from "antd";
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"; import { useState } from "react";
import CodeRecord from "../sharecode/components/CodeRecord";
const { TabPane } = Tabs; const { TabPane } = Tabs;
export default function QuickUploadPage() { export default function QuickUploadPage() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const uploadFileId = Form.useWatch(["file"], form)?.[0] const uploadFileId = Form.useWatch(["file"], form)?.[0]
const [isGetingFileId, setIsGetingFileId] = useState(false);
const handleShareSuccess = (code: string) => { const handleShareSuccess = (code: string) => {
message.success('分享码生成成功:' + code); message.success('分享码生成成功:' + code);
} }
const handleValidSuccess = async (fileUrl: string, fileName: string) => {
setIsGetingFileId(true);
try {
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
message.success('文件下载开始');
} catch (error) {
console.error('下载失败:', error);
message.error('文件下载失败');
} finally {
setIsGetingFileId(false);
}
};
return ( return (
<> <div style={{ maxWidth: '1000px', margin: '0 auto', padding: '20px' }}>
{
isGetingFileId ?
(<Spin spinning={isGetingFileId} fullscreen />) :
(<div style={{ maxWidth: '1000px', margin: '0 auto', padding: '20px' }}>
<div className="flex items-center"> <div className="flex items-center">
<img src="/logo.svg" className="h-16 w-16 rounded-xl" /> <img src="/logo.svg" className="h-16 w-16 rounded-xl" />
<span className="text-4xl py-4 mx-4"></span> <span className="text-4xl py-4 mx-4"></span>
</div> </div>
<Tabs defaultActiveKey="upload"> <Tabs defaultActiveKey="upload">
<TabPane tab="上传分享" key="upload"> <TabPane tab="上传分享" key="upload">
<div className="flex justify-between items-center mb-4">
<span className="text-lg text-zinc-700"></span>
<CodeRecord title="我生成的分享码" btnContent="上传记录" recordName="shareCodeGeneratorRecords" />
</div>
<div> <div>
<Form form={form}> <Form form={form}>
<Form.Item name="file"> <Form.Item name="file">
@ -59,22 +38,20 @@ export default function QuickUploadPage() {
<div style={{ marginBottom: '40px' }}> <div style={{ marginBottom: '40px' }}>
<ShareCodeGenerator <ShareCodeGenerator
fileId={uploadFileId} fileId={uploadFileId}
onSuccess={handleShareSuccess}
/> />
</div> </div>
</TabPane> </TabPane>
<TabPane tab="下载文件" key="download"> <TabPane tab="下载文件" key="download">
<div> <div className="flex justify-between items-center mb-4">
<span className="text-lg block text-zinc-700 py-4">使</span> <span className="text-lg block text-zinc-700 py-4">使</span>
<ShareCodeValidator <CodeRecord title="我使用的分享码" btnContent="使用记录" recordName="shareCodeDownloadRecords" isDownload={true}/>
onValidSuccess={handleValidSuccess} </div>
/> <div>
<ShareCodeValidator />
</div> </div>
</TabPane> </TabPane>
</Tabs> </Tabs>
</div>) </div>
}
</>
) )
} }

View File

@ -0,0 +1,111 @@
import { message } from "antd";
import dayjs from "dayjs";
import { createContext, useContext, useState } from "react";
import { env } from "@web/src/env";
import { api } from "@nice/client";
interface QuickFileContextType {
saveCodeRecord: (data: ShareCodeResponse, recordName: string) => void;
handleValidSuccess: (fileUrl: string, fileName: string) => void;
isGetingFileId: boolean;
downloadCode: string | null;
setDownloadCode: (code: string | null) => void;
refetchShareCodeWithResource: () => Promise<{ data: any }>;
isLoading: boolean;
downloadResult: ShareCodeResponse | null;
}
export interface ShareCodeResponse {
id?: string;
code?: string;
fileName?: string;
expiresAt?: Date;
canUseTimes?: number;
resource?: {
id: string;
type: string;
url: string;
meta: ResourceMeta
}
}
interface ResourceMeta {
filename: string;
filetype: string;
size: string;
}
export const QuickFileContext = createContext<QuickFileContextType | null>(null);
export const QuickFileProvider = ({ children }: { children: React.ReactNode }) => {
const [isGetingFileId, setIsGetingFileId] = useState(false);
const [downloadCode, setDownloadCode] = useState<string | null>(null);
const saveCodeRecord = (data: ShareCodeResponse, recordName: string) => {
const newRecord = {
id: `${Date.now()}`, // 生成唯一ID
code: data.code,
expiresAt: dayjs(data.expiresAt).format('YYYY-MM-DD HH:mm:ss'),
fileName: data.fileName || `文件_${data.code}`,
canUseTimes: data.canUseTimes,
resource: {
id: data.resource.id,
type: data.resource.type,
url: data.resource.url,
meta: {
size: data.resource.meta.size,
filename: data.resource.meta.filename,
filetype: data.resource.meta.filetype
}
}
};
// 获取已有记录并添加新记录
const existingGeneratorRecords = localStorage.getItem(recordName);
const generatorRecords = existingGeneratorRecords ? JSON.parse(existingGeneratorRecords) : [];
generatorRecords.unshift(newRecord); // 添加到最前面
localStorage.setItem(recordName, JSON.stringify(generatorRecords));
}
const handleValidSuccess = async (fileUrl: string, fileName: string) => {
setIsGetingFileId(true);
try {
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
message.success('文件下载开始');
} catch (error) {
console.error('下载失败:', error);
message.error('文件下载失败');
} finally {
setIsGetingFileId(false);
}
};
const { data: downloadResult, isLoading, refetch: refetchShareCodeWithResource } = api.shareCode.getFileByShareCode.useQuery(
{ code: downloadCode?.trim() },
{
enabled: false
}
)
return <>
<QuickFileContext.Provider value={{
saveCodeRecord,
handleValidSuccess,
isGetingFileId,
downloadCode,
setDownloadCode,
refetchShareCodeWithResource,
isLoading,
downloadResult: downloadResult as any as ShareCodeResponse
}}>
{children}
</QuickFileContext.Provider>
</>
};
export const useQuickFileContext = () => {
const context = useContext(QuickFileContext);
if (!context) {
throw new Error("useQuickFileContext must be used within a QuickFileProvider");
}
return context;
};

View File

@ -0,0 +1,88 @@
import { useState, useEffect } from "react";
import { Button, Drawer, Empty } from "antd";
import { HistoryOutlined } from "@ant-design/icons";
import ShareCodeListCard from "../../code-manage/components/ShareCodeListCard";
import { ShareCodeResponse, useQuickFileContext } from "../../quick-file/quickFileContext";
export default function CodeRecord({ title, btnContent, recordName ,styles,isDownload}:
{ title: string, btnContent: string , recordName: string, styles?:string,isDownload?:boolean}) {
const [open, setOpen] = useState(false);
const [records, setRecords] = useState<ShareCodeResponse[]>([]);
const {handleValidSuccess,saveCodeRecord,refetchShareCodeWithResource,setDownloadCode} = useQuickFileContext();
const loadRecordsFromStorage = () => {
const storedRecords = localStorage.getItem(recordName);
if (storedRecords) {
setRecords(JSON.parse(storedRecords));
}
};
useEffect(() => {
loadRecordsFromStorage();
}, [recordName]);
const saveAndUpdateRecord = (data: ShareCodeResponse, name: string) => {
saveCodeRecord(data, name);
if (name === recordName) {
loadRecordsFromStorage();
}
};
const handleOpen = () => {
loadRecordsFromStorage();
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleDelete = (id: string) => {
const updatedRecords = records.filter(record => record.id !== id);
setRecords(updatedRecords);
localStorage.setItem(recordName, JSON.stringify(updatedRecords));
};
const handleDownload = async (code:string) => {
await setDownloadCode(code)
const {data:result} = await refetchShareCodeWithResource()
console.log('下载', result);
handleValidSuccess(result.resource.url, result.fileName);
saveAndUpdateRecord(result as any as ShareCodeResponse, 'shareCodeDownloadRecords');
};
return (
<>
<Button
type="primary"
icon={<HistoryOutlined />}
onClick={handleOpen}
style={{ marginBottom: '16px' }}
>
{btnContent}
</Button>
<Drawer
title={title}
placement="right"
width={420}
onClose={handleClose}
open={open}
>
{records.length > 0 ? (
<div className="space-y-4">
{records.map(item => (
<ShareCodeListCard
key={item.code}
item={item}
onDelete={handleDelete}
onDownload={isDownload ? handleDownload : undefined}
/>
))}
</div>
) : (
<Empty description="暂无分享码记录" />
)}
</Drawer>
</>
);
}

View File

@ -7,6 +7,7 @@ import { api } from '@nice/client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import { ShareCodeResponse, useQuickFileContext } from '../quick-file/quickFileContext';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
interface ShareCodeGeneratorProps { interface ShareCodeGeneratorProps {
@ -14,11 +15,7 @@ interface ShareCodeGeneratorProps {
onSuccess?: (code: string) => void; onSuccess?: (code: string) => void;
fileName?: string; fileName?: string;
} }
interface ShareCodeResponse {
code?: string;
expiresAt?: Date;
canUseTimes?: number;
}
export function copyToClipboard(text) { export function copyToClipboard(text) {
if (navigator.clipboard) { if (navigator.clipboard) {
@ -35,7 +32,6 @@ export function copyToClipboard(text) {
} }
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
fileId, fileId,
onSuccess,
fileName, fileName,
}) => { }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -47,6 +43,7 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
const [isGenerate, setIsGenerate] = useState(false); const [isGenerate, setIsGenerate] = useState(false);
const [currentFileId, setCurrentFileId] = useState<string>(''); const [currentFileId, setCurrentFileId] = useState<string>('');
const [form] = Form.useForm(); const [form] = Form.useForm();
const { saveCodeRecord } = useQuickFileContext();
const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({ const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey }); queryClient.invalidateQueries({ queryKey });
@ -74,10 +71,10 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
console.log('data', data) console.log('data', data)
setShareCode(data.code); setShareCode(data.code);
setIsGenerate(true); setIsGenerate(true);
onSuccess?.(data.code);
setExpiresAt(dayjs(data.expiresAt).format('YYYY-MM-DD HH:mm:ss')); setExpiresAt(dayjs(data.expiresAt).format('YYYY-MM-DD HH:mm:ss'));
setCanUseTimes(data.canUseTimes); setCanUseTimes(data.canUseTimes);
//message.success('分享码生成成功'); saveCodeRecord(data,'shareCodeGeneratorRecords');
message.success('分享码生成成功'+data.code);
} catch (error) { } catch (error) {
console.error('生成分享码错误:', error); console.error('生成分享码错误:', error);
message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误')); message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误'));

View File

@ -1,50 +1,42 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Input, Button, message } from 'antd'; import { Input, Button, message, Spin } from 'antd';
import styles from './ShareCodeValidator.module.css'; import styles from './ShareCodeValidator.module.css';
import { api } from '@nice/client'; import { api } from '@nice/client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
interface ShareCodeValidatorProps {
onValidSuccess: (fileId: string, fileName: string) => void;
}
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import { env } from '@web/src/env'; import { env } from '@web/src/env';
import { copyToClipboard } from './sharecodegenerator'; import { copyToClipboard } from './sharecodegenerator';
import { ShareCodeResponse, useQuickFileContext } from '../quick-file/quickFileContext';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({ export const ShareCodeValidator: React.FC = ({ }) => {
onValidSuccess, const { saveCodeRecord,handleValidSuccess,isGetingFileId,downloadCode,setDownloadCode,refetchShareCodeWithResource,isLoading,downloadResult } = useQuickFileContext();
}) => {
const [code, setCode] = useState('');
const { data: result, isLoading, refetch } = api.shareCode.getFileByShareCode.useQuery(
{ code: code.trim() },
{
enabled: false
}
)
const validateCode = useCallback(async () => { const validateCode = useCallback(async () => {
if (!code.trim()) { if (!downloadCode?.trim()) {
message.warning('请输入正确的分享码'); message.warning('请输入正确的分享码');
return; return;
} }
try { try {
const { data: latestResult } = await refetch(); const { data: latestResult } = await refetchShareCodeWithResource();
console.log('验证分享码返回数据:', latestResult); console.log('验证分享码返回数据:', latestResult);
onValidSuccess(latestResult.url, latestResult.fileName); saveCodeRecord(latestResult as any as ShareCodeResponse,'shareCodeDownloadRecords')
handleValidSuccess(latestResult.resource.url, latestResult.fileName);
} catch (error) { } catch (error) {
console.error('验证分享码失败:', error); console.error('验证分享码失败:', error);
message.error('分享码无效或已过期'); message.error('分享码无效或已过期');
} }
}, [refetch, code, onValidSuccess]); }, [refetchShareCodeWithResource, downloadCode, handleValidSuccess]);
const getDownloadUrl = useCallback(async () => { const getDownloadUrl = useCallback(async () => {
try { try {
const { data: latestResult } = await refetch(); const { data: latestResult } = await refetchShareCodeWithResource();
console.log('验证分享码返回数据:', latestResult); console.log('验证分享码返回数据:', latestResult);
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${latestResult.url}`; const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${latestResult.resource.url}`;
copyToClipboard(downloadUrl) copyToClipboard(downloadUrl)
.then(() => { .then(() => {
message.success(`${latestResult.fileName}的下载链接已复制`,6) message.success(`${latestResult.fileName}的下载链接已复制`, 6)
}) })
.catch(() => { .catch(() => {
message.error('复制失败') message.error('复制失败')
@ -53,14 +45,19 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
console.error('验证分享码失败:', error); console.error('验证分享码失败:', error);
message.error('分享码无效或已过期'); message.error('分享码无效或已过期');
} }
}, [refetch, code, onValidSuccess]); }, [refetchShareCodeWithResource, downloadCode, handleValidSuccess]);
return ( return (
<>
{
isGetingFileId ?
(<Spin spinning={isGetingFileId} fullscreen />) :
(
<> <>
<div className={styles.container}> <div className={styles.container}>
<Input <Input
className={styles.input} className={styles.input}
value={code} value={downloadCode}
onChange={(e) => setCode(e.target.value.toUpperCase())} onChange={(e) => setDownloadCode(e.target.value.toUpperCase())}
placeholder="请输入分享码" placeholder="请输入分享码"
maxLength={8} maxLength={8}
onPressEnter={validateCode} onPressEnter={validateCode}
@ -69,7 +66,7 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
type="primary" type="primary"
onClick={getDownloadUrl} onClick={getDownloadUrl}
loading={isLoading} loading={isLoading}
disabled={!code.trim()} disabled={!downloadCode?.trim()}
> >
</Button> </Button>
@ -77,21 +74,24 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
type="primary" type="primary"
onClick={validateCode} onClick={validateCode}
loading={isLoading} loading={isLoading}
disabled={!code.trim()} disabled={!downloadCode?.trim()}
> >
</Button> </Button>
</div> </div>
{ {
!isLoading && result && ( !isLoading && downloadResult && (
<div className='w-full flex justify-between my-2 p-1 antialiased text-secondary-600'> <div className='w-full flex justify-between my-2 p-1 antialiased text-secondary-600'>
<span >{`分享码:${result?.code ? result.code : ''}`}</span> <span >{`分享码:${downloadResult?.code ? downloadResult.code : ''}`}</span>
<span >{`文件名:${result?.fileName ? result.fileName : ''}`}</span> <span >{`文件名:${downloadResult?.fileName ? downloadResult.fileName : ''}`}</span>
<span >{`过期时间:${result?.expiresAt ? dayjs(result.expiresAt).format('YYYY-MM-DD HH:mm:ss') : ''}`}</span> <span >{`过期时间:${downloadResult?.expiresAt ? dayjs(downloadResult.expiresAt).format('YYYY-MM-DD HH:mm:ss') : ''}`}</span>
<span >{`剩余使用次数:${result?.canUseTimes ? result.canUseTimes : ''}`}</span> <span >{`剩余使用次数:${downloadResult?.canUseTimes ? downloadResult.canUseTimes : ''}`}</span>
</div> </div>
) )
} }
</> </>
)
}
</>
); );
}; };

View File

@ -11,6 +11,7 @@ import WithAuth from "../components/utils/with-auth";
import { CodeManageProvider } from "../app/admin/code-manage/CodeManageContext"; import { CodeManageProvider } from "../app/admin/code-manage/CodeManageContext";
import CodeManageLayout from "../app/admin/code-manage/CodeManageLayout"; import CodeManageLayout from "../app/admin/code-manage/CodeManageLayout";
import QuickUploadPage from "../app/admin/quick-file/page"; import QuickUploadPage from "../app/admin/quick-file/page";
import { QuickFileProvider } from "../app/admin/quick-file/quickFileContext";
interface CustomIndexRouteObject extends IndexRouteObject { interface CustomIndexRouteObject extends IndexRouteObject {
name?: string; name?: string;
breadcrumb?: string; breadcrumb?: string;
@ -34,7 +35,11 @@ export type CustomRouteObject =
export const routes: CustomRouteObject[] = [ export const routes: CustomRouteObject[] = [
{ {
path: "/", path: "/",
element: <QuickUploadPage></QuickUploadPage>, element: <>
<QuickFileProvider>
<QuickUploadPage></QuickUploadPage>
</QuickFileProvider>
</>,
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
}, },
{ {