417 lines
12 KiB
TypeScript
417 lines
12 KiB
TypeScript
import { siteapicontrollerCreatemedia } from '@/servers/api/siteApi';
|
||
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||
import {
|
||
ModalForm,
|
||
PageContainer,
|
||
ProColumns,
|
||
ProFormText,
|
||
ProFormUploadButton,
|
||
ProTable,
|
||
} from '@ant-design/pro-components';
|
||
import { request, useParams } from '@umijs/max';
|
||
import { App, Button, Image, Popconfirm, Space } from 'antd';
|
||
import React, { useState } from 'react';
|
||
|
||
const MediaPage: React.FC = () => {
|
||
const { message } = App.useApp();
|
||
const { siteId } = useParams<{ siteId: string }>();
|
||
const [editing, setEditing] = useState<any>(null);
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||
const actionRef = React.useRef<any>(null);
|
||
|
||
React.useEffect(() => {
|
||
actionRef.current?.reload();
|
||
}, [siteId]);
|
||
|
||
const handleDelete = async (id: number) => {
|
||
if (!siteId) return;
|
||
try {
|
||
const res = await request(`/site-api/${siteId}/media/${id}`, {
|
||
method: 'DELETE',
|
||
});
|
||
if (res.success) {
|
||
message.success('删除成功');
|
||
actionRef.current?.reload();
|
||
} else {
|
||
message.error(res.message || '删除失败');
|
||
}
|
||
} catch (error: any) {
|
||
message.error(error.message || '删除失败');
|
||
}
|
||
};
|
||
|
||
const handleUpdate = async (id: number, data: any) => {
|
||
if (!siteId) return false;
|
||
try {
|
||
const res = await request(`/site-api/${siteId}/media/${id}`, {
|
||
method: 'PUT',
|
||
data,
|
||
});
|
||
if (res.success) {
|
||
message.success('更新成功');
|
||
actionRef.current?.reload();
|
||
return true;
|
||
} else {
|
||
message.error(res.message || '更新失败');
|
||
return false;
|
||
}
|
||
} catch (error: any) {
|
||
message.error(error.message || '更新失败');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
const columns: ProColumns<any>[] = [
|
||
{
|
||
title: 'ID',
|
||
dataIndex: 'id',
|
||
hideInSearch: true,
|
||
width: 120,
|
||
copyable: true,
|
||
render: (_, record) => {
|
||
return record?.id ?? '-';
|
||
},
|
||
},
|
||
{
|
||
title: '展示',
|
||
dataIndex: 'source_url',
|
||
hideInSearch: true,
|
||
render: (_, record) => (
|
||
<Image
|
||
src={record.source_url}
|
||
style={{
|
||
width: 60,
|
||
height: 60,
|
||
objectFit: 'contain',
|
||
background: '#f0f0f0',
|
||
}}
|
||
fallback="https://via.placeholder.com/60?text=No+Img"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '名称',
|
||
dataIndex: 'title',
|
||
copyable: true,
|
||
ellipsis: true,
|
||
width: 200,
|
||
},
|
||
{
|
||
title: '地址',
|
||
dataIndex: 'source_url',
|
||
copyable: true,
|
||
ellipsis: true,
|
||
hideInSearch: true,
|
||
},
|
||
{
|
||
title: '媒体类型',
|
||
dataIndex: 'media_type',
|
||
width: 120,
|
||
},
|
||
{
|
||
title: 'MIME类型',
|
||
dataIndex: 'mime_type',
|
||
width: 120,
|
||
},
|
||
{
|
||
// 文件大小列
|
||
title: '文件大小',
|
||
dataIndex: 'file_size',
|
||
hideInSearch: true,
|
||
width: 120,
|
||
render: (_: any, record: any) => {
|
||
// 获取文件大小
|
||
const fileSize = record.file_size;
|
||
// 如果文件大小不存在,则直接返回-
|
||
if (!fileSize) {
|
||
return '-';
|
||
}
|
||
// 如果文件大小小于1024,则单位为B
|
||
if (fileSize < 1024) {
|
||
return `${fileSize} B`;
|
||
// 如果文件大小小于1024*1024,则单位为KB
|
||
} else if (fileSize < 1024 * 1024) {
|
||
return `${(fileSize / 1024).toFixed(2)} KB`;
|
||
// 否则单位为MB
|
||
} else {
|
||
return `${(fileSize / (1024 * 1024)).toFixed(2)} MB`;
|
||
}
|
||
},
|
||
},
|
||
{
|
||
title: '创建时间',
|
||
dataIndex: 'date_created',
|
||
valueType: 'dateTime',
|
||
hideInSearch: true,
|
||
},
|
||
{
|
||
title: '操作',
|
||
valueType: 'option',
|
||
width: 160,
|
||
fixed: 'right',
|
||
render: (_, record) => (
|
||
<Space>
|
||
<Button
|
||
type="link"
|
||
title="编辑"
|
||
icon={<EditOutlined />}
|
||
onClick={() => {
|
||
setEditing(record);
|
||
}}
|
||
>
|
||
编辑
|
||
</Button>
|
||
<Popconfirm
|
||
title="确定删除吗?"
|
||
onConfirm={() => handleDelete(record.id)}
|
||
okText="确定"
|
||
cancelText="取消"
|
||
>
|
||
<Button type="link" danger title="删除" icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<PageContainer
|
||
ghost
|
||
header={{
|
||
title: null,
|
||
breadcrumb: undefined,
|
||
}}
|
||
>
|
||
<ProTable
|
||
rowKey="id"
|
||
actionRef={actionRef}
|
||
columns={columns}
|
||
rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }}
|
||
scroll={{ x: 'max-content' }}
|
||
request={async (params, sort) => {
|
||
if (!siteId) return { data: [], total: 0 };
|
||
const { current, pageSize } = params || {};
|
||
let orderObj: Record<string, 'asc' | 'desc'> | undefined = undefined;
|
||
if (sort && typeof sort === 'object') {
|
||
const [field, dir] = Object.entries(sort)[0] || [];
|
||
if (field && dir) {
|
||
orderObj = { [field]: dir === 'descend' ? 'desc' : 'asc' };
|
||
}
|
||
}
|
||
const response = await request(`/site-api/${siteId}/media`, {
|
||
params: {
|
||
page: current,
|
||
page_size: pageSize,
|
||
...(orderObj ? { order: orderObj } : {}),
|
||
},
|
||
});
|
||
|
||
if (!response.success) {
|
||
message.error(response.message || '获取媒体列表失败');
|
||
return {
|
||
data: [],
|
||
total: 0,
|
||
success: false,
|
||
};
|
||
}
|
||
|
||
// 从API响应中正确获取数据,API响应结构为 { success, message, data, code }
|
||
const data = response.data;
|
||
return {
|
||
total: data?.total || 0,
|
||
data: data?.items || [],
|
||
success: true,
|
||
};
|
||
}}
|
||
search={false}
|
||
options={{ reload: true }}
|
||
toolBarRender={() => [
|
||
<ModalForm
|
||
title="上传媒体"
|
||
trigger={
|
||
<Button type="primary" title="上传媒体" icon={<PlusOutlined />}>
|
||
上传媒体
|
||
</Button>
|
||
}
|
||
width={500}
|
||
onFinish={async (values) => {
|
||
if (!siteId) return false;
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('siteId', siteId);
|
||
if (values.file && values.file.length > 0) {
|
||
values.file.forEach((f: any) => {
|
||
formData.append('file', f.originFileObj);
|
||
});
|
||
} else {
|
||
message.warning('请选择文件');
|
||
return false;
|
||
}
|
||
|
||
const res = await siteapicontrollerCreatemedia({
|
||
body: formData,
|
||
});
|
||
|
||
if (res.success) {
|
||
message.success('上传成功');
|
||
actionRef.current?.reload();
|
||
return true;
|
||
} else {
|
||
message.error(res.message || '上传失败');
|
||
return false;
|
||
}
|
||
} catch (error: any) {
|
||
message.error(error.message || '上传失败');
|
||
return false;
|
||
}
|
||
}}
|
||
>
|
||
<ProFormUploadButton
|
||
name="file"
|
||
label="文件"
|
||
fieldProps={{
|
||
name: 'file',
|
||
listType: 'picture-card',
|
||
}}
|
||
rules={[{ required: true, message: '请选择文件' }]}
|
||
/>
|
||
</ModalForm>,
|
||
<Button
|
||
title="批量导出"
|
||
onClick={async () => {
|
||
if (!siteId) return;
|
||
const idsParam = selectedRowKeys.length
|
||
? (selectedRowKeys as any[]).join(',')
|
||
: undefined;
|
||
const res = await request(`/site-api/${siteId}/media/export`, {
|
||
params: { ids: idsParam },
|
||
});
|
||
if (res?.success && res?.data?.csv) {
|
||
const blob = new Blob([res.data.csv], {
|
||
type: 'text/csv;charset=utf-8;',
|
||
});
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'media.csv';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
} else {
|
||
message.error(res.message || '导出失败');
|
||
}
|
||
}}
|
||
>
|
||
批量导出
|
||
</Button>,
|
||
<Popconfirm
|
||
title="确定批量删除选中项吗?"
|
||
okText="确定"
|
||
cancelText="取消"
|
||
disabled={!selectedRowKeys.length}
|
||
onConfirm={async () => {
|
||
// 条件判断 如果站点編號不存在則直接返回
|
||
if (!siteId) return;
|
||
// 发起批量删除请求
|
||
const response = await request(
|
||
`/site-api/${siteId}/media/batch`,
|
||
{ method: 'POST', data: { delete: selectedRowKeys } },
|
||
);
|
||
// 条件判断 根据接口返回结果进行提示
|
||
if (response.success) {
|
||
message.success('批量删除成功');
|
||
} else {
|
||
message.warning(response.message || '部分删除失败');
|
||
}
|
||
// 清空已选择的行鍵值
|
||
setSelectedRowKeys([]);
|
||
// 刷新列表数据
|
||
actionRef.current?.reload();
|
||
}}
|
||
>
|
||
<Button
|
||
title="批量删除"
|
||
danger
|
||
icon={<DeleteOutlined />}
|
||
disabled={!selectedRowKeys.length}
|
||
>
|
||
批量删除
|
||
</Button>
|
||
</Popconfirm>,
|
||
|
||
<Button
|
||
title="批量转换为WebP"
|
||
disabled={!selectedRowKeys.length}
|
||
onClick={async () => {
|
||
// 条件判断 如果站点編號不存在則直接返回
|
||
if (!siteId) return;
|
||
try {
|
||
// 发起后端批量转换请求
|
||
const response = await request(
|
||
`/site-api/${siteId}/media/convert-webp`,
|
||
{
|
||
method: 'POST',
|
||
data: { ids: selectedRowKeys },
|
||
},
|
||
);
|
||
// 条件判断 根据接口返回结果进行提示
|
||
if (response.success) {
|
||
const convertedCount = response?.data?.converted?.length || 0;
|
||
const failedCount = response?.data?.failed?.length || 0;
|
||
if (failedCount > 0) {
|
||
message.warning(
|
||
`部分转换失败 已转换 ${convertedCount} 失败 ${failedCount}`,
|
||
);
|
||
} else {
|
||
message.success(`转换成功 已转换 ${convertedCount}`);
|
||
}
|
||
// 刷新列表数据
|
||
actionRef.current?.reload();
|
||
} else {
|
||
message.error(response.message || '转换失败');
|
||
}
|
||
} catch (error: any) {
|
||
message.error(error.message || '转换失败');
|
||
}
|
||
}}
|
||
>
|
||
批量转换为WebP
|
||
</Button>,
|
||
]}
|
||
/>
|
||
|
||
<ModalForm
|
||
title="编辑媒体信息"
|
||
open={!!editing}
|
||
onOpenChange={(visible) => {
|
||
if (!visible) setEditing(null);
|
||
}}
|
||
initialValues={{
|
||
title: editing?.title,
|
||
}}
|
||
modalProps={{
|
||
destroyOnClose: true,
|
||
}}
|
||
onFinish={async (values) => {
|
||
if (!editing) return false;
|
||
const success = await handleUpdate(editing.id, values);
|
||
if (success) {
|
||
setEditing(null);
|
||
}
|
||
return success;
|
||
}}
|
||
>
|
||
<ProFormText
|
||
name="title"
|
||
label="标题"
|
||
placeholder="请输入标题"
|
||
rules={[{ required: true, message: '请输入标题' }]}
|
||
/>
|
||
</ModalForm>
|
||
</PageContainer>
|
||
);
|
||
};
|
||
|
||
export default MediaPage;
|