|
|
@@ -1,9 +1,9 @@
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
-import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, Upload, Popconfirm } from 'antd';
|
|
|
+import { Table, Button, Space, Input, Modal, Form, Select, DatePicker, Upload, Popconfirm, Image } from 'antd';
|
|
|
import { App } from 'antd';
|
|
|
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined } from '@ant-design/icons';
|
|
|
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, EyeOutlined } from '@ant-design/icons';
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
-import { fileClient, clientClient } from '@/client/api';
|
|
|
+import { fileClient } from '@/client/api';
|
|
|
import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
import dayjs from 'dayjs';
|
|
|
import { uploadMinIOWithPolicy } from '@/client/utils/minio';
|
|
|
@@ -11,7 +11,6 @@ import { uploadMinIOWithPolicy } from '@/client/utils/minio';
|
|
|
// 定义类型
|
|
|
type FileItem = InferResponseType<typeof fileClient.$get, 200>['data'][0];
|
|
|
type FileListResponse = InferResponseType<typeof fileClient.$get, 200>;
|
|
|
-type ClientItem = InferResponseType<typeof clientClient.$get, 200>['data'][0];
|
|
|
type UpdateFileRequest = InferRequestType<typeof fileClient[':id']['$put']>['json'];
|
|
|
|
|
|
export const FilesPage: React.FC = () => {
|
|
|
@@ -35,6 +34,51 @@ export const FilesPage: React.FC = () => {
|
|
|
if (!response.ok) throw new Error('Failed to fetch files');
|
|
|
return await response.json() as FileListResponse;
|
|
|
};
|
|
|
+
|
|
|
+ // 获取文件下载URL
|
|
|
+ const getFileUrl = async (fileId: number) => {
|
|
|
+ try {
|
|
|
+ const response = await fileClient[':id']['url'].$get({ param: { id: fileId } });
|
|
|
+ if (!response.ok) throw new Error('获取文件URL失败');
|
|
|
+ const data = await response.json();
|
|
|
+ return data.url;
|
|
|
+ } catch (error) {
|
|
|
+ message.error('获取文件URL失败');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理文件下载
|
|
|
+ const handleDownload = async (record: FileItem) => {
|
|
|
+ const url = await getFileUrl(record.id);
|
|
|
+ if (url) {
|
|
|
+ const a = document.createElement('a');
|
|
|
+ a.href = url;
|
|
|
+ a.download = record.name;
|
|
|
+ document.body.appendChild(a);
|
|
|
+ a.click();
|
|
|
+ document.body.removeChild(a);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理文件预览
|
|
|
+ const handlePreview = async (record: FileItem) => {
|
|
|
+ const url = await getFileUrl(record.id);
|
|
|
+ if (url) {
|
|
|
+ if (record.type.startsWith('image/')) {
|
|
|
+ window.open(url, '_blank');
|
|
|
+ } else if (record.type.startsWith('video/')) {
|
|
|
+ window.open(url, '_blank');
|
|
|
+ } else {
|
|
|
+ message.warning('该文件类型不支持预览');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查是否为可预览的文件类型
|
|
|
+ const isPreviewable = (fileType: string) => {
|
|
|
+ return fileType.startsWith('image/') || fileType.startsWith('video/');
|
|
|
+ };
|
|
|
|
|
|
const { data, isLoading: loading, error: filesError } = useQuery({
|
|
|
queryKey: ['files', pagination.current, pagination.pageSize, searchText],
|
|
|
@@ -159,6 +203,11 @@ export const FilesPage: React.FC = () => {
|
|
|
key: 'name',
|
|
|
width: 300,
|
|
|
ellipsis: true,
|
|
|
+ render: (name: string, record: FileItem) => (
|
|
|
+ <div className="flex items-center">
|
|
|
+ <span className="flex-1">{name}</span>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
},
|
|
|
{
|
|
|
title: '文件类型',
|
|
|
@@ -207,18 +256,32 @@ export const FilesPage: React.FC = () => {
|
|
|
{
|
|
|
title: '操作',
|
|
|
key: 'action',
|
|
|
- width: 120,
|
|
|
+ width: 200,
|
|
|
fixed: 'right' as const,
|
|
|
render: (_: any, record: FileItem) => (
|
|
|
<Space size="small">
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={<EyeOutlined />}
|
|
|
+ onClick={() => handlePreview(record)}
|
|
|
+ className="text-green-600 hover:text-green-800 hover:bg-green-50"
|
|
|
+ disabled={!isPreviewable(record.type)}
|
|
|
+ title={isPreviewable(record.type) ? '预览文件' : '该文件类型不支持预览'}
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={<DownloadOutlined />}
|
|
|
+ onClick={() => handleDownload(record)}
|
|
|
+ className="text-blue-600 hover:text-blue-800 hover:bg-blue-50"
|
|
|
+ title="下载文件"
|
|
|
+ />
|
|
|
<Button
|
|
|
type="text"
|
|
|
icon={<EditOutlined />}
|
|
|
onClick={() => showModal(record)}
|
|
|
- className="text-blue-600 hover:text-blue-800 hover:bg-blue-50"
|
|
|
- >
|
|
|
- 编辑
|
|
|
- </Button>
|
|
|
+ className="text-purple-600 hover:text-purple-800 hover:bg-purple-50"
|
|
|
+ title="编辑文件信息"
|
|
|
+ />
|
|
|
<Popconfirm
|
|
|
title="确认删除"
|
|
|
description={`确定要删除文件"${record.name}"吗?此操作不可恢复。`}
|
|
|
@@ -232,6 +295,7 @@ export const FilesPage: React.FC = () => {
|
|
|
danger
|
|
|
icon={<DeleteOutlined />}
|
|
|
className="hover:bg-red-50"
|
|
|
+ title="删除文件"
|
|
|
>
|
|
|
删除
|
|
|
</Button>
|