Files
timeline-frontend/src/pages/story/components/TimelineItem/TimelineItem.tsx

183 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-08-05 19:02:14 +08:00
import {DeleteOutlined, DownOutlined, EditOutlined, PlusOutlined, UpOutlined} from '@ant-design/icons';
import {useIntl, useRequest} from '@umijs/max';
import { Button, Card, Popconfirm, message } from 'antd';
import React, {useState} from 'react';
import {queryStoryItemImages, removeStoryItem} from '../../service';
import useStyles from './index.style';
import {StoryItem} from "@/pages/story/data";
import TimelineImage from "@/components/TimelineImage";
import TimelineItemDrawer from '../TimelineItemDrawer';
const TimelineItem: React.FC<{
item: StoryItem;
handleOption: (item: StoryItem, option: 'add' | 'edit' | 'addSubItem' | 'editSubItem') => void;
refresh: () => void;
}> = ({ item, handleOption, refresh }) => {
const { styles } = useStyles();
const intl = useIntl();
const [expanded, setExpanded] = useState(false);
const [showActions, setShowActions] = useState(false);
const [subItemsExpanded, setSubItemsExpanded] = useState(false);
const [openDetail, setOpenDetail] = useState(false)
const { data: imagesList } = useRequest(
async () => {
return await queryStoryItemImages(item.instanceId);
},
);
const handleDelete = async () => {
try {
if (!item.instanceId) return;
const response = await removeStoryItem(item.instanceId);
if (response.code === 200) {
message.success(intl.formatMessage({ id: 'story.deleteSuccess' }));
refresh();
} else {
message.error(intl.formatMessage({ id: 'story.deleteFailed' }));
}
} catch (error) {
message.error(intl.formatMessage({ id: 'story.deleteFailed' }));
}
};
const toggleDescription = () => {
setExpanded(!expanded);
};
const toggleSubItems = () => {
setSubItemsExpanded(!subItemsExpanded);
};
const displayedDescription = expanded
? item.description
: item.description?.substring(0, 100) + (item.description && item.description.length > 100 ? '...' : '');
return (
<Card
className={styles.timelineItem}
title={item.title}
onMouseEnter={() => setShowActions(true)}
onMouseLeave={() => setShowActions(false)}
onClick={() => setOpenDetail(true)}
extra={
<div
className={styles.actions}
>
{showActions && (
<>
<Button
type="text"
icon={<EditOutlined />}
onClick={(e) => {
e.stopPropagation();
handleOption(item, 'editSubItem');
}}
aria-label={intl.formatMessage({ id: 'story.edit' })}
/>
<Button
type="text"
icon={<PlusOutlined />}
onClick={(e) => {
e.stopPropagation();
handleOption(item, 'addSubItem');
}}
aria-label={intl.formatMessage({ id: 'story.addSubItem' })}
/>
<Popconfirm
title={intl.formatMessage({ id: 'story.deleteConfirm' })}
description={intl.formatMessage({ id: 'story.deleteConfirmDescription' })}
onConfirm={(e) => {
e?.stopPropagation();
handleDelete()
}}
okText={intl.formatMessage({ id: 'story.yes' })}
cancelText={intl.formatMessage({ id: 'story.no' })}
>
<Button
type="text"
icon={<DeleteOutlined />}
danger
onClick={(e) => e.stopPropagation()}
aria-label={intl.formatMessage({ id: 'story.delete' })}
/>
</Popconfirm>
</>
)}
</div>
}
// onClick={() => onDetail(item)}
hoverable
>
<div className={styles.content}>
<div className={styles.date}>
{item.storyItemTime} {item.location ? `创建于${item.location}` : ''}
</div>
<div className={styles.description}>
{displayedDescription}
{item.description && item.description.length > 100 && (
<Button
type="link"
onClick={(e) => {
e.stopPropagation();
toggleDescription();
}}
>
{expanded
? intl.formatMessage({ id: 'story.showLess' })
: intl.formatMessage({ id: 'story.showMore' })}
</Button>
)}
</div>
{imagesList && imagesList.length > 0 && (
<>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', marginBottom: '20px' }}>
{imagesList.map((imageInstanceId, index) => (
<TimelineImage
key={imageInstanceId + index}
title={imageInstanceId}
imageInstanceId={imageInstanceId}
/>
))}
</div>
</>
)}
{item.subItems && item.subItems.length > 0 && (
<div className={styles.subItems}>
<div
className={styles.subItemsHeader}
onClick={(e) => {
e.stopPropagation();
toggleSubItems();
}}
>
<span>
{intl.formatMessage({ id: 'story.subItems' })} ({item.subItems.length})
</span>
{subItemsExpanded ? <UpOutlined /> : <DownOutlined />}
</div>
{subItemsExpanded && (
<div className={styles.subItemsList}>
{item.subItems.map((subItem) => (
<div key={subItem.id} className={styles.subItem}>
<div className={styles.subItemDate}>
{item.storyItemTime} {item.location ? `创建于${item.location}` : ''}
</div>
<div className={styles.subItemContent}>
{subItem.description}
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
<TimelineItemDrawer
storyItem={item}
open={openDetail}
setOpen={setOpenDetail}
/>
</Card>
);
};
export default TimelineItem;