resource mock data show

This commit is contained in:
penpenwang 2025-12-02 22:09:13 +08:00
parent 0d5781d07a
commit e0b27ad257
6 changed files with 483 additions and 255 deletions

View File

@ -104,7 +104,7 @@ export function GetResourceListApi(
//删除+ 批量删除 //删除+ 批量删除
export function DelResourceItemApi(idList: string) { export function DelResourceItemApi(idList: string) {
return client.destroy(`/backend/v1/jc/softwareInfo?idList=${idList}`); return client.destroy(`/backend/v1/jc/resource/delIds?idList=${idList}`);
} }
//根据id查详情 //根据id查详情
@ -258,8 +258,7 @@ export function AddResourceItemApi(
txtDesc: string, txtDesc: string,
extension: string, extension: string,
size: number, size: number,
path: string, path: string
chapterId?: string
) { ) {
return client.post(`/jc/resource`, { return client.post(`/jc/resource`, {
bookId, bookId,
@ -269,6 +268,27 @@ export function AddResourceItemApi(
extension, extension,
size, size,
path, path,
chapterId, });
}
export function UpdateResourceItemApi(
editId: number,
bookId: number,
name: string,
knowledgeCode: string,
txtDesc: string,
extension: string,
size: number,
path: string
) {
return client.put(`/jc/resource`, {
id: editId,
bookId,
name,
knowledgeCode,
txtDesc,
extension,
size,
path,
}); });
} }

View File

@ -1,8 +1,13 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, message, Spin, Select } from 'antd'; import { Modal, Form, Input, message, Spin, Select, SelectProps } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import TextArea from 'antd/es/input/TextArea'; import TextArea from 'antd/es/input/TextArea';
import { AddResourceItemApi } from '../../../../api/textbook'; import {
AddResourceItemApi,
GetDetailApi,
getKnowledgeListApi,
UpdateResourceItemApi,
} from '../../../../api/textbook';
import { DraggerUpload } from '../Upload/DraggerUpload'; import { DraggerUpload } from '../Upload/DraggerUpload';
interface ModalPropsType { interface ModalPropsType {
@ -13,18 +18,95 @@ interface ModalPropsType {
resourceId: number; resourceId: number;
} }
interface Option { const defaultData = [
value: string | number; {
title: string; createTime: '2025-11-29 16:37:08',
children?: Option[]; updateTime: '2025-12-02 15:42:09',
} creator: null,
updater: null,
tenantId: '-1',
id: 1,
bookId: 46,
parentId: 0,
name: '高等数学基础',
knowledgeCode: 'MATH001',
desc: '高等数学的基础知识点,包含极限、导数、积分等',
level: 1,
type: '基础',
isReal: '1',
orderNum: 1,
extraJson: null,
children: null,
},
{
createTime: '2025-11-29 16:37:08',
updateTime: '2025-12-02 15:42:09',
creator: null,
updater: null,
tenantId: '-1',
id: 2,
bookId: 46,
parentId: 1,
name: '极限与连续',
knowledgeCode: 'MATH001001',
desc: '函数极限的定义与性质,连续函数的特征',
level: 2,
type: '基础',
isReal: '1',
orderNum: 1,
extraJson: null,
children: null,
},
{
createTime: '2025-11-29 16:37:08',
updateTime: '2025-12-02 15:42:09',
creator: null,
updater: null,
tenantId: '-1',
id: 3,
bookId: 46,
parentId: 1,
name: '导数与微分',
knowledgeCode: 'MATH001002',
desc: '导数的定义、计算方法及应用',
level: 2,
type: '基础',
isReal: '1',
orderNum: 2,
extraJson: null,
children: null,
},
{
createTime: '2025-11-29 16:37:08',
updateTime: '2025-12-02 15:42:09',
creator: null,
updater: null,
tenantId: '-1',
id: 4,
bookId: 46,
parentId: 1,
name: '积分学',
knowledgeCode: 'MATH001003',
desc: '不定积分与定积分的计算方法',
level: 2,
type: '基础',
isReal: '1',
orderNum: 3,
extraJson: null,
children: null,
},
];
const CreateResourceModal = (props: ModalPropsType) => { const CreateResourceModal = (props: ModalPropsType) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isOpen, onCancel, bookId, resourceId, isEdit } = props; const { isOpen, onCancel, bookId, resourceId, isEdit } = props;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(false); const [spinInit, setSpinInit] = useState<boolean>(false);
const [spinInit, setSpinInit] = useState(false); const [knowledgeOption, setKnowledgeOption] = useState<
const [knowledgeOption, setKnowledgeOption] = useState<Option[]>([]); {
label: string;
value: number;
}[]
>([]);
const [fileSize, setFileSize] = useState<number>(0); const [fileSize, setFileSize] = useState<number>(0);
const [fileName, setFileName] = useState<string>(''); const [fileName, setFileName] = useState<string>('');
const [filePath, setFilePath] = useState<string>(''); const [filePath, setFilePath] = useState<string>('');
@ -36,77 +118,87 @@ const CreateResourceModal = (props: ModalPropsType) => {
if (isEdit && resourceId) { if (isEdit && resourceId) {
getDetail(); getDetail();
} else { } else {
form.resetFields();
setSpinInit(false); setSpinInit(false);
form.setFieldsValue({
type: '视频',
});
} }
}, [isEdit, resourceId]); }, [isEdit, resourceId]);
/* useEffect(() => { useEffect(() => {
getChapterTreeData(); getKnowledgeOptionData();
}, []);*/ }, []);
/* const getKnowledgeOptionData = () => { useEffect(() => {
GetChapterTreeApi({ bookId }).then((res: any) => { return () => {
form.resetFields();
setFileName('');
setFilePath('');
setFileSize(0);
setKnowledgeCode('');
};
}, []);
const getKnowledgeOptionData = () => {
getKnowledgeListApi(bookId).then((res: any) => {
const resData: any = res.data; const resData: any = res.data;
// console.log(resData, '>>>'); const knowledgeOption: { label: string; value: number }[] = defaultData.map(
setKnowledgeOption(resData); (item: { name: string; id: number }) => ({
label: `${item.name}`, // 如:"极限与连续 (基础)" (${item.type})
value: item.id,
})
);
setKnowledgeOption(knowledgeOption);
}); });
};*/ };
const getDetail = () => { const getDetail = () => {
GetDetailApi(resourceId)
.then((res: any) => {
if (!res || !res.data) {
message.error('获取详情失败');
setSpinInit(false); setSpinInit(false);
return;
}
})
.catch((err) => {
message.error('获取详情失败');
setSpinInit(false);
console.error('获取详情失败:', err);
});
}; };
const onFinish = (values: any) => { const onFinish = (values: any) => {
console.log('表单提交:', values); const { name = '', txtDesc = '', knowledgeCode = '' } = values;
const { name = '', txtDesc = '', chapterId = '' } = values;
try { try {
if (isEdit) { if (isEdit) {
/*UpdateTextbookApi( UpdateResourceItemApi(
editId, resourceId,
values.title,
thumb,
values.short_desc,
values.author,
values.major,
dep_ids,
group_ids,
user_ids,
values.publish_time,
values.publish_unit,
values.create_time,
values.isbn
)
.then((res: any) => {
setLoading(false);
message.success(t('commen.saveSuccess'));
onCancel();
})
.catch((e) => {
setLoading(false);
});*/
} else {
AddResourceItemApi(
bookId, bookId,
name, name,
knowledgeCode, knowledgeCode,
txtDesc, txtDesc,
fileExtension, fileExtension,
fileSize, fileSize,
filePath, filePath
chapterId
) )
.then((res: any) => { .then((res: any) => {
setLoading(false); setSpinInit(false);
message.success(t('commen.saveSuccess')); message.success(t('commen.saveSuccess'));
onCancel(); onCancel();
}) })
.catch((e) => { .catch((e) => {
setLoading(false); setSpinInit(false);
});
} else {
console.log(bookId, name, knowledgeCode, txtDesc, fileExtension, fileSize, filePath);
AddResourceItemApi(bookId, name, knowledgeCode, txtDesc, fileExtension, fileSize, filePath)
.then((res: any) => {
setSpinInit(false);
message.success(t('commen.saveSuccess'));
onCancel();
})
.catch((e) => {
console.log(e, 'error');
setSpinInit(false);
}); });
} }
onCancel(); onCancel();
@ -117,6 +209,7 @@ const CreateResourceModal = (props: ModalPropsType) => {
const handleSelectChange = (e: any) => { const handleSelectChange = (e: any) => {
console.log(e, '>>.select e'); console.log(e, '>>.select e');
setKnowledgeCode(e.join(','));
}; };
const onFinishFailed = (errorInfo: any) => { const onFinishFailed = (errorInfo: any) => {
@ -160,14 +253,6 @@ const CreateResourceModal = (props: ModalPropsType) => {
> >
<TextArea allowClear placeholder={t('textbook.resource.descPlaceholder')} /> <TextArea allowClear placeholder={t('textbook.resource.descPlaceholder')} />
</Form.Item> </Form.Item>
{/* <Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">{t('textbook.resource.uploadTips')}</p>
<p className="ant-upload-hint">{t('textbook.resource.uploadTips2')}</p>
</Dragger>*/}
<Form.Item <Form.Item
name="path" name="path"
label={t('textbook.resource.upload')} label={t('textbook.resource.upload')}
@ -215,15 +300,20 @@ const CreateResourceModal = (props: ModalPropsType) => {
<Form.Item <Form.Item
name="knowledge" name="knowledge"
label={t('textbook.resource.knowledgeId')} label={t('textbook.resource.knowledgeId')}
rules={[{ required: true, message: t('textbook.resource.konwledgePlaceholder') }]} rules={[{ required: true, message: t('textbook.resource.knowledgePlaceholder') }]}
> >
<Select <Select
mode="multiple" mode="multiple"
placeholder="Please select" placeholder={t('textbook.resource.knowledgePlaceholder')}
defaultValue={[]} defaultValue={knowledgeCode ? [knowledgeCode] : []}
onChange={handleSelectChange} onChange={handleSelectChange}
style={{ width: '100%' }} style={{ width: '100%' }}
options={knowledgeOption} options={knowledgeOption}
showSearch
//@ts-ignore
filterOption={(input: string, option: { label: string; value: number }) =>
(option?.label ?? '')?.toLowerCase().includes(input.toLowerCase())
}
/> />
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from 'react';
import { Upload, message, Progress, List, Button, Space } from 'antd'; import { Upload, message, Progress, List, Button, Space } from 'antd';
import { InboxOutlined, CloseOutlined, FileOutlined } from '@ant-design/icons'; import { InboxOutlined, CloseOutlined, FileOutlined } from '@ant-design/icons';
import type { UploadProps } from 'antd'; import type { UploadProps } from 'antd';
import { getTenant, getToken } from '../../../../utils'; import { formatFileSize, getTenant, getToken } from '../../../../utils';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import config from '../../../../js/config'; import config from '../../../../js/config';
@ -92,7 +92,6 @@ export const DraggerUpload = (props: IProps) => {
// 检查文件类型(仅对其他类型做限制) // 检查文件类型(仅对其他类型做限制)
const checkFileType = (file: File): boolean => { const checkFileType = (file: File): boolean => {
if (typeId && typeId !== 5) return true; if (typeId && typeId !== 5) return true;
const extension = getFileExtension(file.name); const extension = getFileExtension(file.name);
const restrictedExtensions = [ const restrictedExtensions = [
'mp4', 'mp4',
@ -119,7 +118,6 @@ export const DraggerUpload = (props: IProps) => {
message.error(t('textbook.resource.restrictedFileType')); message.error(t('textbook.resource.restrictedFileType'));
return false; return false;
} }
return true; return true;
}; };
@ -285,19 +283,13 @@ export const DraggerUpload = (props: IProps) => {
return false; return false;
} }
return checkFileType(file); if (typeId) {
checkFileType(file);
}
return true;
}, },
}; };
// 格式化文件大小
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// 获取状态对应的文本和颜色 // 获取状态对应的文本和颜色
const getStatusInfo = (status: UploadFileItem['status']) => { const getStatusInfo = (status: UploadFileItem['status']) => {
switch (status) { switch (status) {

View File

@ -25,14 +25,15 @@
justify-content: space-between; justify-content: space-between;
margin-bottom: 16px; margin-bottom: 16px;
.btns{ .btns{
width: 70%; width: 56%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: end; justify-content: end;
align-items: baseline; align-items: baseline;
} }
.types{ .types{
width: 30%; min-width: 475px;
width: 44%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: start; justify-content: start;

View File

@ -1,4 +1,4 @@
import { Button, Input, message, Modal, Radio, Space, Table, TableProps } from 'antd'; import { Button, Image, Input, message, Modal, Radio, Space, Table, TableProps, Tag } from 'antd';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { GetResourceListApi, DelResourceItemApi, GetDetailApi } from '../../api/textbook'; import { GetResourceListApi, DelResourceItemApi, GetDetailApi } from '../../api/textbook';
import styles from './resource.module.less'; import styles from './resource.module.less';
@ -7,28 +7,54 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import LoadingPage from '../loading'; import LoadingPage from '../loading';
import { BackBartment } from '../../compenents'; import { BackBartment } from '../../compenents';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const MusicSvg: React.FC = () => (
import { DeleteOutlined, UploadOutlined } from '@ant-design/icons'; <svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
>
<path
d="M320 192 1024 0 1024 64 1024 192 1024 736C1024 824.352 923.712 896 800 896 676.288 896 576 824.352 576 736 576 647.648 676.288 576 800 576 834.368 576 866.912 581.536 896 591.392L896 261.824 448 384 448 864C448 952.352 347.712 1024 224 1024 100.288 1024 0 952.352 0 864 0 775.648 100.288 704 224 704 258.368 704 290.912 709.536 320 719.392L320 384 320 192Z"
fill="#628eee"
></path>
</svg>
);
import {
DeleteOutlined,
EyeOutlined,
FileTextFilled,
FileTextTwoTone,
FileUnknownTwoTone,
PlayCircleOutlined,
UploadOutlined,
VideoCameraFilled,
} from '@ant-design/icons';
import CreateResourceModal from './compenents/Resource/CreateResourceModal'; import CreateResourceModal from './compenents/Resource/CreateResourceModal';
import { dateFormatNoTime, formatFileSize } from '../../utils';
import defaultThumb1 from '../../assets/thumb/thumb1.png';
interface ResourceBase { interface ResourceBase {
id: number | null | string | undefined; // 资源id id: string; // 资源id
typeId: number; // 分类Id typeId?: number | string; // 分类Id
bookId: 0; bookId: 0;
chapterId: 0; chapterId?: 0;
chapterTitle?: string;
name: string; name: string;
type: string; type: string;
ext: string; ext?: string;
size: 0; size: 0;
duration: 0; duration?: 0;
url: string; url: string;
cover: string; cover?: string;
status: 0; status?: 0;
creator: string; creator?: string;
updater: string; updater?: string;
createTime: string; createTime: string;
updateTime: string; updateTime?: string;
tenantId: string; tenantId?: string;
} }
// 列表展示 // 列表展示
@ -51,6 +77,8 @@ const ResourcePage = () => {
const { bookId } = useParams(); const { bookId } = useParams();
const [isEdit, setIsEdit] = useState(false); // 是否编辑模式 const [isEdit, setIsEdit] = useState(false); // 是否编辑模式
const [editId, setEditId] = useState<number>(0); const [editId, setEditId] = useState<number>(0);
const [itemDetailData, setItemDetailData] = useState<ResourceBase>();
const [loading, setLoading] = useState<boolean>(false);
const [isAddModalOpen, setIsAddModalOpen] = useState<boolean>(false); const [isAddModalOpen, setIsAddModalOpen] = useState<boolean>(false);
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
@ -76,42 +104,159 @@ const ResourcePage = () => {
{ label: t('textbook.resource.typeList.audio'), value: 4, color: '#f3e8ff' }, { label: t('textbook.resource.typeList.audio'), value: 4, color: '#f3e8ff' },
{ label: t('textbook.resource.typeList.other'), value: 5, color: '#f3f4f6' }, { label: t('textbook.resource.typeList.other'), value: 5, color: '#f3f4f6' },
]; ];
const bgColors = ['#dbeafe', '#dcfce7', '#fef9c3', '#f3e8ff', '#f3f4f6'];
const fontColors = ['#4649c1', '#1a6838', '#98692b', '#6e26aa', '#434b57'];
// 排序字段/**/ // 排序字段/**/
/* name /* name
size createTime这三个*/ size createTime这三个*/
// 列表数据 // 列表数据
const demoData = [
{
key: '1',
id: '1',
name: '课程介绍视频.mp4',
chapterTitle: '第1章 课程概述 - 1.1 课程简介',
type: '视频',
typeId: '1',
createTime: '2024-01-15',
size: 12500000, // 用于排序的数值
},
{
key: '2',
id: '2',
name: '课程大纲图.jpg',
chapterTitle: '第1章 课程概述 - 1.1 课程简介',
type: '图片',
typeId: '2',
url: '',
createTime: '2024-01-10',
size: 2400000,
},
{
id: '3',
key: '3',
name: '第一套练习题.pdf',
chapterTitle: '第1章 课程概述',
type: '文档',
typeId: '3',
createTime: '2024-01-12',
size: 1800000,
},
{
id: '4',
key: '4',
name: '课程导入音频.mp3',
chapterTitle: '第1章 课程概述 - 1.1 课程简介',
type: '音频',
typeId: '4',
createTime: '2024-01-14',
size: 5200000,
},
];
const columns: TableProps<ResourceBase>['columns'] = [ const columns: TableProps<ResourceBase>['columns'] = [
{ {
title: t('textbook.resource.title1'), title: t('textbook.resource.title1'),
dataIndex: 'softNo', dataIndex: 'name',
align: 'left', align: 'left',
width: 300, width: 500,
sorter: true, sorter: true,
render: (name: string, record) => (
<div className="d-flex">
<div
style={{
width: 58,
height: 58,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '6px',
background: '#f3f4f6',
textAlign: 'center',
marginRight: 15,
overflow: 'hidden',
}}
>
{record.typeId == '1' && (
<VideoCameraFilled style={{ fontSize: 30, color: '#628eee' }} />
)}{' '}
{record.typeId == '2' && (
<Image
src={record.url}
width={58}
height={58}
preview={false}
fallback={defaultThumb1}
/>
)}
{record.typeId == '3' && <FileTextFilled style={{ fontSize: 30, color: '#628eee' }} />}
{record.typeId == '4' && <MusicSvg />}
{record.typeId == '5' && (
<FileUnknownTwoTone style={{ fontSize: 30, color: '#628eee' }} />
)}
</div>
<div>
<div style={{ fontWeight: 'bold', fontSize: '14px', marginBottom: '4px' }}>{name}</div>
<div style={{ color: '#666', fontSize: '12px' }}>{record?.chapterTitle}</div>
</div>
</div>
),
}, },
{ {
title: t('textbook.resource.title2'), title: t('textbook.resource.title2'),
dataIndex: 'softwareName', dataIndex: 'typeId',
align: 'left', key: 'typeId',
align: 'center',
width: 100,
render: (typeId: number, record) => {
return (
<div
style={{
borderRadius: '20px',
width: 60,
height: 30,
lineHeight: '30px',
backgroundColor: bgColors[Number(typeId) - 1],
color: fontColors[Number(typeId) - 1],
}}
>
{record.type}
</div>
);
},
}, },
{ {
title: t('textbook.resource.title3'), title: t('textbook.resource.title3'),
dataIndex: 'company', dataIndex: 'size',
ellipsis: true, ellipsis: true,
sorter: true, sorter: true,
key: 'size',
width: 150,
align: 'center',
render: (size) => <span style={{ fontWeight: 500 }}>{formatFileSize(size)}</span>,
}, },
{ {
title: t('textbook.resource.title4'), title: t('textbook.resource.title4'),
dataIndex: 'version',
align: 'left', align: 'left',
ellipsis: true, ellipsis: true,
width: 140, width: 180,
sorter: true, sorter: true,
dataIndex: 'createTime',
key: 'createTime',
render: (createTime: string) => {
return <span style={{ color: 'gray' }}>{dateFormatNoTime(createTime)}</span>;
},
}, },
{ {
title: t('textbook.resource.title5'), title: t('textbook.resource.title5'),
// align: 'center', align: 'center',
render: (_, record) => ( width: 240,
render: (_, record) => {
return (
<Space <Space
size="middle" size="middle"
style={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }} style={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }}
@ -122,90 +267,55 @@ const ResourcePage = () => {
console.log('预览'); console.log('预览');
}} }}
> >
{/*<EyeOutlined />*/}
{t('commen.preview')} {t('commen.preview')}
</a> </a>
<a <a
key={'edit'} key={'edit'}
//@ts-ignore
onClick={() => { onClick={() => {
getDetail(Number(record.id)); setEditId(Number(record.id));
setIsAddModalOpen(true);
}} }}
> >
{t('commen.edit')} {t('commen.edit')}
</a> </a>
<a <a key={'del'} className="b-link c-red" onClick={() => showDeleteConfirm(record.id)}>
key={'del'}
className="b-link c-red"
onClick={() => showDeleteConfirm(Number(record.id))}
>
{/*<DeleteOutlined />*/} {/*<DeleteOutlined />*/}
{t('commen.del')} {t('commen.del')}
</a> </a>
</Space> </Space>
), );
},
}, },
]; ];
const getResourceList = () => {
GetResourceListApi(page, pageSize, type, sortOrder, sortField, searchData)
.then((res: any) => {
// @ts-ignore // @ts-ignore
const getResourceList = async () => { setResource(demoData);
try { setResourceTotal(demoData.length);
const res = (await GetResourceListApi(
page,
pageSize,
type,
searchData,
sortOrder,
sortField
)) as ResourceResData;
if (res && 'data' in res && 'records' in res.data) {
setResource(res.data.records);
setResourceTotal(res.data.total); setResourceTotal(res.data.total);
} else { setLoading(false);
console.warn('接口返回数据结构异常:', res); })
setResource([]); // 设置为空数组防止崩溃 .catch((error) => {
} setLoading(false);
} catch (error) { setResource([]);
console.error('获取列表失败:', error); });
}
}; };
const resetVirtualList = async () => { const resetVirtualList = () => {
try { GetResourceListApi(1, 10, null, null, null, null)
const res = (await GetResourceListApi(1, 10, null, null, null, null)) as ResourceResData; .then((res: any) => {
if (res && 'data' in res && 'records' in res.data) {
setResource(res.data.records || []); setResource(res.data.records || []);
setResourceTotal(res.data.total); setResourceTotal(res.data.total);
} else { })
console.warn('接口返回数据结构异常:', res); .catch((error) => {
setResource([]); // 设置为空数组防止崩溃 console.warn('接口返回数据结构异常:', error);
} setResource([]);
} catch (error) { });
console.error('获取列表失败:', error);
}
}; };
const getDetail = async (id: number) => { const showDeleteConfirm = (id: string) => {
try {
const res = (await GetDetailApi(id)) as VirtualResDetail | undefined;
if (!res || !res.data) {
message.error('获取详情失败');
return;
}
setIsEdit(true);
//@ts-ignore
setItemDetailData(res.data);
setSelectedId(res.data.id);
// 打开弹窗
setIsAddModalOpen(true);
} catch (error) {
message.error('获取详情失败');
setPageLoading(false);
console.error('获取详情失败:', error);
}
};
const showDeleteConfirm = (id: number) => {
setSelectedId(id); setSelectedId(id);
setModalVisible(true); setModalVisible(true);
}; };
@ -213,31 +323,33 @@ const ResourcePage = () => {
const handleDeleteItem = async () => { const handleDeleteItem = async () => {
if (selectedId === null) return; if (selectedId === null) return;
setConfirmLoading(true); setConfirmLoading(true);
try { DelResourceItemApi(selectedId.toString())
await DelResourceItemApi(selectedId.toString()); .then(() => {
message.success('删除成功'); message.success('删除成功');
// @ts-ignore getResourceList();
await getVirtualList();
} catch (error) {
message.error('删除失败');
console.error('删除失败:', error);
} finally {
setConfirmLoading(false); setConfirmLoading(false);
setModalVisible(false); setModalVisible(false);
setSelectedId(null); setSelectedId(null);
} })
.catch((err) => {
setConfirmLoading(false);
setModalVisible(false);
setSelectedId(null);
message.error('删除失败');
console.error('删除失败:', err);
});
}; };
//弹窗取消
const showAddSoftModal = () => { const showAddSoftModal = () => {
setIsEdit(false); // 设置为新增模式 setIsEdit(false);
setSelectedId(null); // 清除选中 ID setSelectedId(null);
setIsAddModalOpen(true); setIsAddModalOpen(true);
}; };
const handleCancelDeleteItem = () => { const handleCancelDeleteItem = () => {
setModalVisible(false); setModalVisible(false);
}; };
const handleCancelDeleteItems = () => { const handleCancelDeleteItems = () => {
setIsConfirmModalOpen(false); setIsConfirmModalOpen(false);
}; };
@ -247,6 +359,8 @@ const ResourcePage = () => {
setType(0); setType(0);
setSelectedIdList([]); setSelectedIdList([]);
setSearchData(''); setSearchData('');
setSortOrder('');
setSortField('');
resetVirtualList(); resetVirtualList();
}; };
// 批量删除 // 批量删除
@ -254,22 +368,25 @@ const ResourcePage = () => {
toDeleteSoftwareList(); toDeleteSoftwareList();
}; };
const toDeleteSoftwareList = async () => { const toDeleteSoftwareList = () => {
try {
const selectedIdListString = selectedIdList.join(','); const selectedIdListString = selectedIdList.join(',');
const res = await DelResourceItemApi(selectedIdListString); DelResourceItemApi(selectedIdListString)
.then(() => {
message.success('删除成功'); message.success('删除成功');
// @ts-ignore
await getVirtualList();
} catch (error) {
message.error('删除失败');
console.error('删除失败:', error);
} finally {
setConfirmLoading(false); setConfirmLoading(false);
setIsConfirmModalOpen(false); setIsConfirmModalOpen(false);
setSelectedId(null); setSelectedId(null);
setSelectedIdList([]); setSelectedIdList([]);
} getResourceList();
})
.catch((err) => {
message.error('删除失败');
console.error('删除失败:', err);
setConfirmLoading(false);
setIsConfirmModalOpen(false);
setSelectedId(null);
setSelectedIdList([]);
});
}; };
const handleAddCancel = () => { const handleAddCancel = () => {
@ -282,7 +399,7 @@ const ResourcePage = () => {
useEffect(() => { useEffect(() => {
setResource([]); setResource([]);
getResourceList(); getResourceList();
}, [refresh, page, pageSize, type, searchData]); }, [refresh, page, pageSize, type, sortOrder, sortField]);
const onSelectChange = (newSelectedRowKeys: any[]) => { const onSelectChange = (newSelectedRowKeys: any[]) => {
setSelectedIdList(newSelectedRowKeys); setSelectedIdList(newSelectedRowKeys);
@ -294,26 +411,21 @@ const ResourcePage = () => {
onChange: onSelectChange, onChange: onSelectChange,
}; };
useEffect(() => {
getResourceList();
}, [page, pageSize, type, sortOrder, sortField, searchData]);
const handleTableChange = ( const handleTableChange = (
pagination: { current: number; pageSize: number }, pagination: { current: number; pageSize: number },
filters: any, filters: any,
sorter: { field: string; order: string } sorter: { field: string; order: string }
) => { ) => {
// 统一处理分页 // 统一处理分页
// if (pagination.current !== page || pagination.pageSize !== pageSize) {
setPage(pagination.current); setPage(pagination.current);
setPageSize(pagination.pageSize); setPageSize(pagination.pageSize);
// }
// 处理排序 // 处理排序
if (sorter && sorter.field) { if (sorter && sorter.field) {
const sortField = sorter.field as string; const sField = sorter.field as string;
const sortOrder = const sOrder = sorter.order === 'ascend' ? 'asc' : sorter.order === 'descend' ? 'desc' : '';
sorter.order === 'ascend' ? 'asc' : sorter.order === 'descend' ? 'desc' : '';
console.log('排序字段:', sortField, '排序方向:', sortOrder); console.log('排序字段:', sortField, '排序方向:', sortOrder);
setSortField(sField);
setSortOrder(sOrder);
} }
}; };
@ -363,7 +475,7 @@ const ResourcePage = () => {
<div className={styles.btns}> <div className={styles.btns}>
<Input <Input
placeholder={t('textbook.resource.searchPlaceholder')} placeholder={t('textbook.resource.searchPlaceholder')}
style={{ marginRight: 15, width: 360 }} style={{ marginRight: 15, width: 220 }}
value={searchData} value={searchData}
allowClear allowClear
onChange={(e) => setSearchData(e.target.value)} onChange={(e) => setSearchData(e.target.value)}
@ -405,8 +517,8 @@ const ResourcePage = () => {
current: page, current: page,
total: resourceTotal, total: resourceTotal,
showSizeChanger: true, showSizeChanger: true,
align: 'start', align: 'end',
showTotal: (total) => `${resourceTotal} 条记录`, showTotal: () => `${resourceTotal} 条记录`,
}} }}
// @ts-ignore // @ts-ignore
onChange={handleTableChange} onChange={handleTableChange}
@ -431,7 +543,7 @@ const ResourcePage = () => {
onCancel={handleCancelDeleteItem} onCancel={handleCancelDeleteItem}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
> >
<p></p> <p></p>
</Modal> </Modal>
{/*多个删除确认*/} {/*多个删除确认*/}
<Modal <Modal
@ -441,7 +553,7 @@ const ResourcePage = () => {
onCancel={handleCancelDeleteItems} onCancel={handleCancelDeleteItems}
confirmLoading={confirmLoading} confirmLoading={confirmLoading}
> >
<p></p> <p></p>
</Modal> </Modal>
</> </>
); );

View File

@ -408,3 +408,16 @@ export const dataURLtoBlob = (dataurl: string): Blob => {
export const getFileExtension = (filename: string): string => { export const getFileExtension = (filename: string): string => {
return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2); return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2);
}; };
/**
*
*/
// 格式化文件大小
export const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};