forum
This commit is contained in:
@@ -32,8 +32,12 @@ const ForumDetail = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const hasFetched = React.useRef(false);
|
||||
useEffect(() => {
|
||||
fetchTopic();
|
||||
if (!hasFetched.current) {
|
||||
fetchTopic();
|
||||
hasFetched.current = true;
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const handleSubmitReply = async () => {
|
||||
@@ -122,8 +126,45 @@ const ForumDetail = () => {
|
||||
minHeight: 200,
|
||||
whiteSpace: 'pre-wrap' // Preserve formatting
|
||||
}}>
|
||||
{topic.content}
|
||||
{topic.content.replace(/!\[.*?\]\(.*?\)/g, '[图片]')}
|
||||
</div>
|
||||
|
||||
{(() => {
|
||||
const regexMatches = topic.content.match(/!\[.*?\]\((.*?)\)/g);
|
||||
const regexImages = regexMatches ? regexMatches.map(match => match.match(/!\[.*?\]\((.*?)\)/)[1]) : [];
|
||||
|
||||
// 优先使用 Markdown 中解析出的图片(保持顺序)
|
||||
if (regexImages.length > 0) {
|
||||
return regexImages.map((url, index) => (
|
||||
<div key={`regex-${index}`} style={{ marginTop: 12 }}>
|
||||
<img
|
||||
src={url}
|
||||
alt="content"
|
||||
style={{ maxHeight: 400, borderRadius: 8, maxWidth: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
// 兜底:如果 Markdown 解析失败或未插入但已上传,显示关联的媒体资源
|
||||
if (topic.media && topic.media.length > 0) {
|
||||
return topic.media.map((media) => (
|
||||
<div key={`media-${media.id}`} style={{ marginTop: 12 }}>
|
||||
{media.media_type === 'video' ? (
|
||||
<video src={media.url} controls style={{ maxHeight: 400, borderRadius: 8, maxWidth: '100%' }} />
|
||||
) : (
|
||||
<img
|
||||
src={media.url}
|
||||
alt="content"
|
||||
style={{ maxHeight: 400, borderRadius: 8, maxWidth: '100%' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
})()}
|
||||
</Card>
|
||||
|
||||
{/* Replies List */}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Typography, Input, Button, List, Tag, Avatar, Card, Space, Spin, messag
|
||||
import { SearchOutlined, PlusOutlined, UserOutlined, MessageOutlined, EyeOutlined, CheckCircleFilled, FireOutlined, StarFilled, QuestionCircleOutlined, ShareAltOutlined, SoundOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getTopics } from '../api';
|
||||
import { getTopics, getStarUsers, getAnnouncements } from '../api';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import CreateTopicModal from '../components/CreateTopicModal';
|
||||
import LoginModal from '../components/LoginModal';
|
||||
@@ -16,6 +16,8 @@ const ForumList = () => {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [topics, setTopics] = useState([]);
|
||||
const [starUsers, setStarUsers] = useState([]);
|
||||
const [announcements, setAnnouncements] = useState([]);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [category, setCategory] = useState('all');
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
@@ -38,8 +40,28 @@ const ForumList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchStarUsers = async () => {
|
||||
try {
|
||||
const res = await getStarUsers();
|
||||
setStarUsers(res.data);
|
||||
} catch (error) {
|
||||
console.error("Fetch star users failed", error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAnnouncements = async () => {
|
||||
try {
|
||||
const res = await getAnnouncements();
|
||||
setAnnouncements(res.data.results || res.data);
|
||||
} catch (error) {
|
||||
console.error("Fetch announcements failed", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTopics(searchText, category);
|
||||
fetchStarUsers();
|
||||
fetchAnnouncements();
|
||||
}, [category]);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
@@ -176,8 +198,18 @@ const ForumList = () => {
|
||||
ellipsis={{ rows: 2 }}
|
||||
style={{ color: '#aaa', marginBottom: 12, fontSize: 14 }}
|
||||
>
|
||||
{item.content.replace(/[#*`]/g, '')} {/* Simple markdown strip */}
|
||||
{item.content.replace(/!\[.*?\]\(.*?\)/g, '[图片]').replace(/[#*`]/g, '')} {/* Simple markdown strip */}
|
||||
</Paragraph>
|
||||
|
||||
{item.content.match(/!\[.*?\]\((.*?)\)/) && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<img
|
||||
src={item.content.match(/!\[.*?\]\((.*?)\)/)[1]}
|
||||
alt="cover"
|
||||
style={{ maxHeight: 150, borderRadius: 8, maxWidth: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Space size="middle" style={{ color: '#666', fontSize: 13 }}>
|
||||
<Space>
|
||||
@@ -224,21 +256,21 @@ const ForumList = () => {
|
||||
headStyle={{ borderBottom: '1px solid rgba(255,255,255,0.1)' }}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: '20px 0', color: '#666' }}>
|
||||
{/* 这里可以通过 API 获取专家列表,目前先做静态展示或从帖子中提取 */}
|
||||
<div style={{ marginBottom: 15, display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=Expert1" />
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ color: '#fff', fontWeight: 'bold' }}>QuantMaster <StarFilled style={{ color: '#ffd700', fontSize: 12 }} /></div>
|
||||
<div style={{ color: '#666', fontSize: 12 }}>官方技术支持</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 15, display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<Avatar size="large" src="https://api.dicebear.com/7.x/avataaars/svg?seed=Expert2" />
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ color: '#fff', fontWeight: 'bold' }}>AI_Wizard <StarFilled style={{ color: '#ffd700', fontSize: 12 }} /></div>
|
||||
<div style={{ color: '#666', fontSize: 12 }}>社区贡献者</div>
|
||||
</div>
|
||||
</div>
|
||||
{starUsers.length > 0 ? (
|
||||
starUsers.map(u => (
|
||||
<div key={u.id} style={{ marginBottom: 15, display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<Avatar size="large" src={u.avatar_url} icon={<UserOutlined />} />
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ color: '#fff', fontWeight: 'bold' }}>
|
||||
{u.nickname} <StarFilled style={{ color: '#ffd700', fontSize: 12 }} />
|
||||
</div>
|
||||
<div style={{ color: '#666', fontSize: 12 }}>{u.title || '技术专家'}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div style={{ color: '#888' }}>暂无上榜专家</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -249,12 +281,27 @@ const ForumList = () => {
|
||||
>
|
||||
<List
|
||||
size="small"
|
||||
dataSource={[
|
||||
'欢迎来到 Quant Speed 开发者社区',
|
||||
'发帖前请阅读社区规范',
|
||||
'如何获取“认证用户”标识?'
|
||||
]}
|
||||
renderItem={item => <List.Item style={{ color: '#aaa', padding: '8px 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>{item}</List.Item>}
|
||||
dataSource={announcements}
|
||||
renderItem={item => (
|
||||
<List.Item style={{ padding: '12px 0', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'block' }}>
|
||||
{item.display_image_url && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<img src={item.display_image_url} alt={item.title} style={{ width: '100%', borderRadius: 4 }} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ color: '#fff', marginBottom: 4, fontWeight: 'bold' }}>
|
||||
{item.link_url ? (
|
||||
<a href={item.link_url} target="_blank" rel="noopener noreferrer" style={{ color: '#fff' }}>{item.title}</a>
|
||||
) : (
|
||||
<span>{item.title}</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ color: '#888', fontSize: 12 }}>
|
||||
{item.content}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: <div style={{ color: '#666', padding: '20px 0', textAlign: 'center' }}>暂无公告</div> }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
Reference in New Issue
Block a user