This commit is contained in:
jeremygan2021
2026-02-12 15:02:53 +08:00
parent b4ac97c3c2
commit 9e81eaaaab
23 changed files with 844 additions and 104 deletions

View File

@@ -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 */}

View File

@@ -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>