支持缩略图加载,故事项新建调整

This commit is contained in:
jiangh277
2025-08-06 18:41:32 +08:00
parent 141e8d9818
commit 182a58d0db
11 changed files with 448 additions and 229 deletions

View File

@@ -1,13 +1,29 @@
// src/pages/list/basic-list/components/AddTimeLineItemModal.tsx
import chinaRegion, { code2Location } from '@/commonConstant/chinaRegion';
import { addStoryItem } from '@/pages/story/service';
import { UploadOutlined } from '@ant-design/icons';
import { getImagesList } from '@/services/file/api'; // 引入获取图库图片的API
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
import { useRequest } from '@umijs/max';
import { Button, Cascader, DatePicker, Form, Input, message, Modal, Upload } from 'antd';
import ImgCrop from 'antd-img-crop';
import {
Button,
Card,
Cascader,
Checkbox,
DatePicker,
Form,
Image,
Input,
InputRef,
message,
Modal,
Pagination,
Spin,
Tabs,
Upload,
} from 'antd';
import dayjs from 'dayjs';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
interface ModalProps {
visible: boolean;
@@ -31,18 +47,61 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
const [form] = Form.useForm();
const [fileList, setFileList] = useState<any[]>([]);
const [imageList, setImageList] = useState<any[]>(initialValues?.images || []);
const [galleryImages, setGalleryImages] = useState<any[]>([]); // 图库图片列表
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
initialValues?.galleryImageIds || [],
); // 选中的图库图片
const [galleryLoading, setGalleryLoading] = useState(false);
const [activeTab, setActiveTab] = useState('upload'); // 图片选择标签页
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(20);
const [total, setTotal] = useState(0);
const [searchKeyword, setSearchKeyword] = useState('');
const searchInputRef = useRef<InputRef>(null);
useEffect(() => {
if (initialValues && option === 'edit') {
if (initialValues && option.startsWith('edit')) {
form.setFieldsValue({
title: initialValues.title,
storyItemTime: initialValues.date ? moment(initialValues.date) : undefined,
date: initialValues.storyItemTime ? moment(initialValues.storyItemTime) : undefined,
location: initialValues.location,
description: initialValues.description,
cover: initialValues.cover ? [{ url: initialValues.cover }] : [],
images: initialValues.images?.map((url) => ({ url })) || [],
});
}
}, [initialValues, option]);
// 获取图库图片
const fetchGalleryImages = async (page: number = 1, keyword: string = '') => {
if (visible) {
setGalleryLoading(true);
try {
const response = await getImagesList({
current: page,
pageSize: pageSize,
keyword: keyword,
});
const images = response.data.list.map((img: any) => ({
instanceId: img.instanceId,
imageName: img.imageName,
url: `/file/image-low-res/${img.instanceId}`,
}));
setGalleryImages(images);
setTotal(response.data.total);
setCurrentPage(page);
} catch (error) {
message.error('获取图库图片失败');
} finally {
setGalleryLoading(false);
}
}
};
useEffect(() => {
if (visible && activeTab === 'gallery') {
fetchGalleryImages(1, searchKeyword);
}
}, [visible, activeTab, searchKeyword]);
const { run: submitItem, loading } = useRequest((newItem) => addStoryItem(newItem), {
manual: true,
onSuccess: (data) => {
@@ -63,16 +122,17 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
const values = await form.validateFields();
const location = code2Location(values.location);
const newItem = {
...values,
id: initialValues?.id || Date.now(),
storyItemTime: dayjs(values.date).format('YYYY-MM-DDTHH:mm:ss'),
masterItemId: initialValues.masterItemId,
title: values.title,
description: values.description,
masterItemId: initialValues?.masterItemId,
subItems: initialValues?.subItems || [],
storyInstanceId: storyId,
location,
instanceId: initialValues?.instanceId,
// 添加选中的图库图片ID
relatedImageInstanceIds: selectedGalleryImages,
};
delete newItem.cover;
delete newItem.images;
// 构建 FormData
const formData = new FormData();
@@ -83,7 +143,8 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
if (fileList.length > 0 && fileList[0].originFileObj instanceof File) {
formData.append('cover', fileList[0].originFileObj);
}
console.log(imageList);
// 添加上传的图片文件
if (imageList.length > 0) {
imageList.forEach((file) => {
if (file.originFileObj && file.originFileObj instanceof File) {
@@ -91,6 +152,8 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
}
});
}
console.log(formData);
// 提交
submitItem(formData);
} catch (error) {
@@ -99,26 +162,6 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
}
};
const uploadCoverProps = {
beforeUpload: (file) => {
// 确保 originFileObj 是真正的 File 对象
return false; // 阻止自动上传
},
onChange: ({ fileList }) => {
// 确保 originFileObj 是真正的 File 对象
const updatedFileList = fileList.map((file) => {
if (file.originFileObj && !(file.originFileObj instanceof File)) {
file.originFileObj = new File([file.originFileObj], file.name, { type: file.type });
}
return file;
});
setFileList(updatedFileList);
},
listType: 'picture',
maxCount: 1,
defaultFileList: initialValues?.cover ? [{ url: initialValues.cover }] : [],
};
const uploadImagesProps = {
beforeUpload: () => false,
onChange: ({ fileList }) => {
@@ -135,14 +178,149 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
defaultFileList: initialValues?.images?.map((url) => ({ url })),
};
// 切换图库图片选择状态
const toggleGalleryImageSelection = (instanceId: string) => {
setSelectedGalleryImages((prev) => {
if (prev.includes(instanceId)) {
return prev.filter((id) => id !== instanceId);
} else {
return [...prev, instanceId];
}
});
};
// 处理分页变化
const handlePageChange = (page: number) => {
fetchGalleryImages(page, searchKeyword);
};
// 处理搜索
const handleSearch = (value: string) => {
setSearchKeyword(value);
fetchGalleryImages(1, value);
};
// 渲染图库图片选择器
const renderGallerySelector = () => (
<div>
{/* 搜索框 */}
<div style={{ marginBottom: 16 }}>
<Input
ref={searchInputRef}
placeholder="搜索图片名称"
prefix={<SearchOutlined />}
onPressEnter={(e) => handleSearch(e.currentTarget.value)}
style={{ width: 200 }}
allowClear
/>
<Button
type="primary"
onClick={() => handleSearch(searchInputRef.current?.input?.value || '')}
style={{ marginLeft: 8 }}
>
</Button>
</div>
{galleryLoading ? (
<div style={{ textAlign: 'center', padding: '50px 0' }}>
<Spin size="large" />
</div>
) : (
<>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
gap: '10px',
maxHeight: '300px',
overflowY: 'auto',
}}
>
{galleryImages.map((image) => (
<Card
key={image.instanceId}
size="small"
hoverable
onClick={() => toggleGalleryImageSelection(image.instanceId)}
style={{
border: selectedGalleryImages.includes(image.instanceId)
? '2px solid #1890ff'
: '1px solid #f0f0f0',
position: 'relative',
}}
>
<div style={{ position: 'relative' }}>
<Image
src={image.url}
alt={image.imageName}
style={{ width: '100%', height: '100px', objectFit: 'cover' }}
preview={false}
/>
<Checkbox
checked={selectedGalleryImages.includes(image.instanceId)}
style={{
position: 'absolute',
top: '4px',
right: '4px',
}}
onClick={(e) => e.stopPropagation()}
/>
</div>
<div
style={{
fontSize: '12px',
marginTop: '4px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
title={image.imageName}
>
{image.imageName}
</div>
</Card>
))}
</div>
{galleryImages.length === 0 && !galleryLoading && (
<div style={{ textAlign: 'center', padding: '20px' }}>
{searchKeyword ? '未找到相关图片' : '图库中暂无图片'}
</div>
)}
{/* 分页器 */}
{total > 0 && (
<div style={{ marginTop: 16, textAlign: 'right' }}>
<Pagination
current={currentPage}
pageSize={pageSize}
total={total}
onChange={handlePageChange}
showSizeChanger={false}
showQuickJumper
size="small"
/>
</div>
)}
</>
)}
</div>
);
return (
<Modal
open={visible}
title={initialValues ? '编辑时间点' : '添加时间点'}
onCancel={() => {
form.resetFields();
setSelectedGalleryImages([]);
setSearchKeyword('');
setCurrentPage(1);
setGalleryImages([]);
onCancel();
}}
width={800}
footer={[
<Button key="back" onClick={onCancel}>
@@ -153,7 +331,9 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
]}
>
<Form form={form} layout="vertical">
{['editSubItem', 'addSubItem'].includes( option) && <Form.Item label={'主时间点'}>{storyItemId}</Form.Item>}
{['editSubItem', 'addSubItem'].includes(option) && (
<Form.Item label={'主时间点'}>{initialValues.title}</Form.Item>
)}
<Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]}>
<Input placeholder="请输入标题" />
</Form.Item>
@@ -176,21 +356,36 @@ const AddTimeLineItemModal: React.FC<ModalProps> = ({
<Input.TextArea rows={4} placeholder="请输入时间点描述" />
</Form.Item>
<Form.Item label="封面图" name="cover">
<ImgCrop aspect={1} quality={0.5} rotationSlider>
<Upload {...uploadCoverProps} maxCount={1}>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</ImgCrop>
</Form.Item>
{/* 新增:时刻图库 */}
<Form.Item label="时刻图库(多图)" name="images">
<Upload {...uploadImagesProps} maxCount={5}>
<Button size={'small'} icon={<UploadOutlined />}>
</Button>
</Upload>
{/* 时刻图库 */}
<Form.Item label="附图" name="images">
<Tabs
activeKey={activeTab}
onChange={(key) => {
setActiveTab(key);
if (key === 'gallery' && galleryImages.length === 0) {
fetchGalleryImages(1, searchKeyword);
}
}}
items={[
{
key: 'upload',
label: '上传图片',
children: (
<Upload {...uploadImagesProps} maxCount={5}>
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}></div>
</div>
</Upload>
),
},
{
key: 'gallery',
label: `从图库选择${selectedGalleryImages.length > 0 ? ` (${selectedGalleryImages.length})` : ''}`,
children: renderGallerySelector(),
},
]}
/>
</Form.Item>
</Form>
</Modal>