feat: 文件下载

This commit is contained in:
2026-02-28 17:26:13 +08:00
parent 62abb284a6
commit 4418a95822
9 changed files with 155 additions and 31 deletions

View File

@@ -17,7 +17,7 @@ export default {
// http://192.168.1.231:48080 伟强 // http://192.168.1.231:48080 伟强
// http://192.168.1.89:48086 子杰 // http://192.168.1.89:48086 子杰
// https://petshy.tashowz.com/ // https://petshy.tashowz.com/
target: 'http://192.168.1.89:48086', target: 'http://192.168.1.89:48080',
changeOrigin: true, changeOrigin: true,
}, },
}, },

View File

@@ -32,6 +32,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
const [type, setType] = useState<'group' | 'tag'>('group'); const [type, setType] = useState<'group' | 'tag'>('group');
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
const [name, setName] = useState<string>(''); const [name, setName] = useState<string>('');
const [tagName, setTagName] = useState<string>('');
const [total, setTotal] = useState<number>(0); const [total, setTotal] = useState<number>(0);
const [currentId, setCurrentId] = useState<number>(); const [currentId, setCurrentId] = useState<number>();
const [tagsModalValue, setTagsModalValue] = useState<{ const [tagsModalValue, setTagsModalValue] = useState<{
@@ -56,7 +57,11 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
const fetchTagsApi = useCallback(async () => { const fetchTagsApi = useCallback(async () => {
try { try {
setLoadingTags(true); setLoadingTags(true);
const res = await tagsApi.get({ groupId: currentId, pageNo: 1 }); const res = await tagsApi.get({
groupId: currentId,
pageNo: 1,
tagName: tagName,
});
const newGroup = { ...currentGroup, tags: res.list }; const newGroup = { ...currentGroup, tags: res.list };
const newData = groups.map((g) => (g.id === currentId ? newGroup : g)); const newData = groups.map((g) => (g.id === currentId ? newGroup : g));
setGroups(newData as GroupItem[]); setGroups(newData as GroupItem[]);
@@ -238,6 +243,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
const onSearchTags = async (e: React.KeyboardEvent<HTMLInputElement>) => { const onSearchTags = async (e: React.KeyboardEvent<HTMLInputElement>) => {
const searchValue = (e.target as HTMLInputElement).value; const searchValue = (e.target as HTMLInputElement).value;
setTagName(searchValue);
const res = await tagsApi.get({ const res = await tagsApi.get({
groupId: currentId, groupId: currentId,
pageNo: 1, pageNo: 1,
@@ -247,11 +253,15 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
...currentGroup, ...currentGroup,
tags: res.list, tags: res.list,
}; };
setTotal(res.total);
document.getElementById('scrollableDiv')?.scrollTo(0, 0); document.getElementById('scrollableDiv')?.scrollTo(0, 0);
setCurrentGroup(newGroup as GroupItem); setCurrentGroup(newGroup as GroupItem);
setPageNo(1); setPageNo(1);
}; };
const onSearchTagsClear = async () => {
setTagName('');
fetchTagsApi();
};
return ( return (
<div className={`group-tag-core`}> <div className={`group-tag-core`}>
<div className="search-wrapper"> <div className="search-wrapper">
@@ -260,6 +270,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
placeholder="搜索" placeholder="搜索"
onPressEnter={onSearchTags} onPressEnter={onSearchTags}
allowClear allowClear
onClear={onSearchTagsClear}
disabled={isInEditMode} disabled={isInEditMode}
/> />
</div> </div>

View File

@@ -116,7 +116,7 @@ export const renderTags = (data: GroupTagProps) => {
<Empty description="暂无分组" image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty description="暂无分组" image={Empty.PRESENTED_IMAGE_SIMPLE} />
); );
} }
console.log(list.length); console.log(list, total);
return ( return (
<InfiniteScroll <InfiniteScroll
dataLength={list.length} dataLength={list.length}

View File

@@ -32,7 +32,7 @@ const ModelPage = () => {
}; };
const onFetch = async (params: { pageSize?: number; current?: number }) => { const onFetch = async (params: { pageSize?: number; current?: number }) => {
const data = await getModelList(params); const data = await getModelList({ ...params, pageNo: params.current });
return { return {
data: data.list, data: data.list,
success: true, success: true,

View File

@@ -1,4 +1,5 @@
import type { ProColumns } from '@ant-design/pro-components'; import type { ProColumns } from '@ant-design/pro-components';
import { Tag } from 'antd';
import GroupTagSelect from '@/components/GroupTag/GroupTagSelect'; import GroupTagSelect from '@/components/GroupTag/GroupTagSelect';
import { import {
type AiSampleRespVO, type AiSampleRespVO,
@@ -15,13 +16,32 @@ export const baseTenantColumns: ProColumns<AiSampleRespVO>[] = [
{ {
title: '样本名称', title: '样本名称',
dataIndex: 'sampleName', dataIndex: 'sampleName',
// width: 500, width: 200,
ellipsis: true,
},
{
title: '文件格式',
width: 100,
dataIndex: 'sampleMineType',
},
{
title: '标签',
dataIndex: 'tags',
width: 200,
hideInSearch: true,
render: (_, record) => {
return record.tags?.map((tag) => {
return <Tag key={tag.id}>{tag.tagName}</Tag>;
});
},
}, },
{ {
title: '注释', title: '注释',
width: 100,
dataIndex: 'remark', dataIndex: 'remark',
hideInSearch: true, hideInSearch: true,
}, },
{ {
title: '标签', title: '标签',
hideInTable: true, hideInTable: true,
@@ -57,11 +77,6 @@ export const baseTenantColumns: ProColumns<AiSampleRespVO>[] = [
); );
}, },
}, },
{
title: '样本格式',
hideInTable: true,
dataIndex: 'sample_mine_type',
},
]; ];
// export const formColumns = (data: { // export const formColumns = (data: {

View File

@@ -20,6 +20,8 @@ import {
deleteSampleTag, deleteSampleTag,
deleteSampleTagGroup, deleteSampleTagGroup,
deleteSampleTagRelate, deleteSampleTagRelate,
downloadSample,
downloadZipFile,
getSampleTagGroup, getSampleTagGroup,
getSampleTagPage, getSampleTagPage,
relateSample, relateSample,
@@ -123,7 +125,22 @@ const SampleTagDetail = <T extends Record<string, any>>(
}; };
// 下载 // 下载
const handleDownloadAll = () => {}; const handleDownloadAll = async () => {
const ids = data?.map((sample) => sample.id) as number[];
downloadZipFile(ids);
// const res = await downloadSample(ids);
// console.log(res);
// const downloadUrl = window.URL.createObjectURL(res);
// const link = document.createElement("a");
// link.href = downloadUrl;
// link.download = downloadUrl; // 设置下载的文件名
// link.style.display = "none";
// document.body.appendChild(link);
// link.click();
// document.body.removeChild(link);
// // const blob = new Blob([response.data], { type: 'application/pdf' });
// // const url = window.URL.createObjectURL(blob);
};
const handleTagManager = () => { const handleTagManager = () => {
setTagManagerVisible(true); setTagManagerVisible(true);
@@ -158,6 +175,16 @@ const SampleTagDetail = <T extends Record<string, any>>(
setTagManagerVisible(false); setTagManagerVisible(false);
}; };
const onDownload = () => {
const item = data?.[0];
const link = document.createElement('a');
link.href = item?.sampleFilePath;
link.download = item?.sampleName; // 设置下载的文件名
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return ( return (
<> <>
<ProForm name="validate_other" formRef={formRef} submitter={false}> <ProForm name="validate_other" formRef={formRef} submitter={false}>
@@ -312,7 +339,7 @@ const SampleTagDetail = <T extends Record<string, any>>(
onCancel={() => setTagManagerVisible(false)} onCancel={() => setTagManagerVisible(false)}
></TagManager> ></TagManager>
<Space style={{ width: '100%', justifyContent: 'center', padding: 12 }}> <Space style={{ width: '100%', justifyContent: 'center', padding: 12 }}>
<Button color="danger" onClick={() => {}}> <Button color="danger" onClick={onDownload}>
</Button> </Button>
<Button color="danger" variant="solid" onClick={handleDeleteAll}> <Button color="danger" variant="solid" onClick={handleDeleteAll}>

View File

@@ -58,6 +58,7 @@ const SampleTag: React.FC = () => {
const onFetch = async (params: SampleReqVo) => { const onFetch = async (params: SampleReqVo) => {
const data = await getSamplePage({ const data = await getSamplePage({
...params, ...params,
pageNo: params.current,
}); });
return { return {
data: data.list, data: data.list,
@@ -86,7 +87,7 @@ const SampleTag: React.FC = () => {
headerTitle="样本列表" headerTitle="样本列表"
showIndex={false} showIndex={false}
enableRowClick={true} enableRowClick={true}
scroll={{ x: 'max-content' }} scroll={{ x: 400 }}
rowSelection={{ rowSelection={{
type: selectTableType, type: selectTableType,
selectedRowKeys: selectedRows.map((item) => item.id) as React.Key[], selectedRowKeys: selectedRows.map((item) => item.id) as React.Key[],

View File

@@ -134,23 +134,20 @@ export const errorConfig: RequestConfig = {
const { data } = response as unknown as ResponseStructure; const { data } = response as unknown as ResponseStructure;
const config = response.config; const config = response.config;
const { code } = data; const { code } = data;
// if (!data) {
// // 返回“[HTTP]请求没有返回值”; if (!data) {
// throw new Error(); // 返回“[HTTP]请求没有返回值”;
// } throw new Error();
// 未设置状态码则默认成功状态 }
// 二进制数据则直接返回,例如说 Excel 导出
// if ( if (
// response.request.responseType === "blob" || response.request.responseType === 'blob' ||
// response.request.responseType === "arraybuffer" response.request.responseType === 'arraybuffer'
// ) { ) {
// // 注意:如果导出的响应为 json说明可能失败了不直接返回进行下载 return response;
// // if (response.data.type !== "application/json") { // data = await new Response(data).json();
// // return response.data; }
// // } // 获取错误信息
// data = await new Response(data).json();
// }
// // 获取错误信息
// const msg = data.msg || errorCode[code] || errorCode["default"]; // const msg = data.msg || errorCode[code] || errorCode["default"];
// if (ignoreMsgs.indexOf(msg) !== -1) { // if (ignoreMsgs.indexOf(msg) !== -1) {
// // 如果是忽略的错误码,直接返回 msg 异常 // // 如果是忽略的错误码,直接返回 msg 异常

View File

@@ -1,4 +1,5 @@
import { request } from "@umijs/max"; import { request } from "@umijs/max";
import { message } from "antd";
export interface SampleVo { export interface SampleVo {
/** /**
@@ -85,9 +86,11 @@ export interface AiSampleRespVO {
* 样本时长 * 样本时长
*/ */
sampleTime?: string; sampleTime?: string;
tags?: { tagName?: string; id?: number }[];
} }
export interface SampleReqVo extends PageParam { export interface SampleReqVo extends PageParam {
current: number | undefined;
name?: string; name?: string;
status?: number; status?: number;
} }
@@ -244,3 +247,73 @@ export const updateSampleTag = async (params: {
data: params, data: params,
}); });
}; };
// 下载
export const downloadSample = async (ids: number[]) => {
return request("/ai/sample/download", {
method: "GET",
params: { ids },
responseType: "blob",
});
};
export async function downloadZipFile(ids: number[]) {
try {
const response = await downloadSample(ids);
const blob = response; // Blob {size: 3164203, type: 'application/zip'}
// 验证文件类型
if (blob.type !== "application/zip" && !blob.type.includes("zip")) {
console.warn("返回的可能不是 ZIP 文件:", blob.type);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = `音频文件_${new Date().getTime()}.wav`; // 设置文件名和后缀
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return;
}
// 从响应头获取文件名
let fileName = "音频文件";
const contentDisposition =
response.response?.headers?.["content-disposition"];
if (!fileName && contentDisposition) {
const match = contentDisposition.match(
/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
);
if (match?.[1]) {
fileName = decodeURIComponent(match[1].replace(/['"]/g, ""));
}
}
// 默认文件名
fileName = fileName || `archive_${new Date().getTime()}.zip`;
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
message.success(
`下载成功,文件大小:${(blob.size / 1024 / 1024).toFixed(2)} MB`
);
return true;
} catch (error) {
console.error("下载失败:", error);
message.error("下载失败,请稍后重试");
return false;
}
}