283 lines
12 KiB
JavaScript
283 lines
12 KiB
JavaScript
// src/views/Community/components/EventDetailModal.js
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Modal, Spin, Descriptions, Tag, List, Badge, Empty, Input, Button, message } from 'antd';
|
||
import { eventService } from '../../../services/eventService';
|
||
import { logger } from '../../../utils/logger';
|
||
import moment from 'moment';
|
||
|
||
const EventDetailModal = ({ visible, event, onClose }) => {
|
||
const [loading, setLoading] = useState(false);
|
||
const [eventDetail, setEventDetail] = useState(null);
|
||
const [commentText, setCommentText] = useState('');
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [comments, setComments] = useState([]);
|
||
const [commentsLoading, setCommentsLoading] = useState(false);
|
||
|
||
const loadEventDetail = async () => {
|
||
if (!event) return;
|
||
|
||
setLoading(true);
|
||
try {
|
||
const response = await eventService.getEventDetail(event.id);
|
||
if (response.success) {
|
||
setEventDetail(response.data);
|
||
}
|
||
} catch (error) {
|
||
logger.error('EventDetailModal', 'loadEventDetail', error, {
|
||
eventId: event?.id
|
||
});
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const loadComments = async () => {
|
||
if (!event) return;
|
||
|
||
setCommentsLoading(true);
|
||
try {
|
||
// 使用统一的posts API获取评论
|
||
const result = await eventService.getPosts(event.id);
|
||
if (result.success) {
|
||
setComments(result.data || []);
|
||
}
|
||
} catch (error) {
|
||
logger.error('EventDetailModal', 'loadComments', error, {
|
||
eventId: event?.id
|
||
});
|
||
} finally {
|
||
setCommentsLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (visible && event) {
|
||
loadEventDetail();
|
||
loadComments();
|
||
}
|
||
}, [visible, event]);
|
||
|
||
const getImportanceColor = (importance) => {
|
||
const colors = {
|
||
S: 'red',
|
||
A: 'orange',
|
||
B: 'blue',
|
||
C: 'green'
|
||
};
|
||
return colors[importance] || 'default';
|
||
};
|
||
|
||
const getRelationDesc = (relationDesc) => {
|
||
// 处理空值
|
||
if (!relationDesc) return '';
|
||
|
||
// 如果是字符串,直接返回
|
||
if (typeof relationDesc === 'string') {
|
||
return relationDesc;
|
||
}
|
||
|
||
// 如果是对象且包含data数组
|
||
if (typeof relationDesc === 'object' && relationDesc.data && Array.isArray(relationDesc.data)) {
|
||
const firstItem = relationDesc.data[0];
|
||
if (firstItem) {
|
||
// 优先使用 query_part,其次使用 sentences
|
||
return firstItem.query_part || firstItem.sentences || '';
|
||
}
|
||
}
|
||
|
||
// 其他情况返回空字符串
|
||
return '';
|
||
};
|
||
|
||
const renderPriceTag = (value, label) => {
|
||
if (value === null || value === undefined) return `${label}: --`;
|
||
|
||
const color = value > 0 ? '#ff4d4f' : '#52c41a';
|
||
const prefix = value > 0 ? '+' : '';
|
||
|
||
return (
|
||
<span>
|
||
{label}: <span style={{ color }}>{prefix}{value.toFixed(2)}%</span>
|
||
</span>
|
||
);
|
||
};
|
||
|
||
const handleSubmitComment = async () => {
|
||
if (!commentText.trim()) {
|
||
message.warning('请输入评论内容');
|
||
return;
|
||
}
|
||
setSubmitting(true);
|
||
try {
|
||
// 使用统一的createPost API
|
||
const result = await eventService.createPost(event.id, {
|
||
content: commentText.trim(),
|
||
content_type: 'text'
|
||
});
|
||
|
||
if (result.success) {
|
||
message.success('评论发布成功');
|
||
setCommentText('');
|
||
// 重新加载评论列表
|
||
loadComments();
|
||
} else {
|
||
throw new Error(result.message || '评论失败');
|
||
}
|
||
} catch (e) {
|
||
message.error(e.message || '评论失败');
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
title={eventDetail?.title || '事件详情'}
|
||
open={visible}
|
||
onCancel={onClose}
|
||
width={800}
|
||
footer={null}
|
||
>
|
||
<Spin spinning={loading}>
|
||
{eventDetail && (
|
||
<>
|
||
<Descriptions bordered column={2} style={{ marginBottom: 24 }}>
|
||
<Descriptions.Item label="创建时间">
|
||
{moment(eventDetail.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="创建者">
|
||
{eventDetail.creator?.username || 'Anonymous'}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="重要性">
|
||
<Badge color={getImportanceColor(eventDetail.importance)} text={`${eventDetail.importance}级`} />
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="浏览数">
|
||
{eventDetail.view_count || 0}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="涨幅统计" span={2}>
|
||
<Tag>{renderPriceTag(eventDetail.related_avg_chg, '平均涨幅')}</Tag>
|
||
<Tag>{renderPriceTag(eventDetail.related_max_chg, '最大涨幅')}</Tag>
|
||
<Tag>{renderPriceTag(eventDetail.related_week_chg, '周涨幅')}</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="事件描述" span={2}>
|
||
{eventDetail.description}(AI合成)
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
{eventDetail.keywords && eventDetail.keywords.length > 0 && (
|
||
<div style={{ marginBottom: 24 }}>
|
||
<h4>相关概念</h4>
|
||
{eventDetail.keywords.map((keyword, index) => (
|
||
<Tag key={index} color="blue" style={{ marginBottom: 8 }}>
|
||
{keyword}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{eventDetail.related_stocks && eventDetail.related_stocks.length > 0 && (
|
||
<div>
|
||
<h4>相关股票</h4>
|
||
<List
|
||
size="small"
|
||
dataSource={eventDetail.related_stocks}
|
||
renderItem={stock => (
|
||
<List.Item
|
||
actions={[
|
||
<Button
|
||
type="primary"
|
||
size="small"
|
||
onClick={() => {
|
||
const stockCode = stock.stock_code.split('.')[0];
|
||
window.open(`https://valuefrontier.cn/company?scode=${stockCode}`, '_blank');
|
||
}}
|
||
>
|
||
股票详情
|
||
</Button>
|
||
]}
|
||
>
|
||
<List.Item.Meta
|
||
title={`${stock.stock_name} (${stock.stock_code})`}
|
||
description={getRelationDesc(stock.relation_desc) ? `${getRelationDesc(stock.relation_desc)}(AI合成)` : ''}
|
||
/>
|
||
{stock.change !== null && (
|
||
<Tag color={stock.change > 0 ? 'red' : 'green'}>
|
||
{stock.change > 0 ? '+' : ''}{stock.change.toFixed(2)}%
|
||
</Tag>
|
||
)}
|
||
</List.Item>
|
||
)}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* 讨论区 */}
|
||
<div style={{ marginTop: 24 }}>
|
||
<h4>讨论区</h4>
|
||
|
||
{/* 评论列表 */}
|
||
<div style={{ marginBottom: 24 }}>
|
||
<Spin spinning={commentsLoading}>
|
||
{comments.length === 0 ? (
|
||
<Empty
|
||
description="暂无评论"
|
||
style={{ padding: '20px 0' }}
|
||
/>
|
||
) : (
|
||
<List
|
||
itemLayout="vertical"
|
||
dataSource={comments}
|
||
renderItem={comment => (
|
||
<List.Item key={comment.id}>
|
||
<List.Item.Meta
|
||
title={
|
||
<div style={{ fontSize: '14px' }}>
|
||
<strong>{comment.author?.username || 'Anonymous'}</strong>
|
||
<span style={{ marginLeft: 8, color: '#999', fontWeight: 'normal' }}>
|
||
{moment(comment.created_at).format('MM-DD HH:mm')}
|
||
</span>
|
||
</div>
|
||
}
|
||
description={
|
||
<div style={{
|
||
fontSize: '14px',
|
||
lineHeight: '1.6',
|
||
marginTop: 8
|
||
}}>
|
||
{comment.content}
|
||
</div>
|
||
}
|
||
/>
|
||
</List.Item>
|
||
)}
|
||
/>
|
||
)}
|
||
</Spin>
|
||
</div>
|
||
|
||
{/* 评论输入框(登录后可用,未登录后端会返回401) */}
|
||
<div>
|
||
<h4>发表评论</h4>
|
||
<Input.TextArea
|
||
placeholder="说点什么..."
|
||
rows={3}
|
||
value={commentText}
|
||
onChange={(e) => setCommentText(e.target.value)}
|
||
maxLength={500}
|
||
showCount
|
||
/>
|
||
<div style={{ textAlign: 'right', marginTop: 8 }}>
|
||
<Button type="primary" loading={submitting} onClick={handleSubmitComment}>
|
||
发布
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</Spin>
|
||
</Modal>
|
||
);
|
||
};
|
||
|
||
export default EventDetailModal; |