feat: 文件下载
This commit is contained in:
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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[],
|
||||||
|
|||||||
@@ -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 异常
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user