This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Typography, Card, Avatar, Tag, Space, Button, Divider, Input, message, Upload, Tooltip, Grid, Modal } from 'antd';
|
||||
import { UserOutlined, ClockCircleOutlined, EyeOutlined, CheckCircleFilled, LeftOutlined, UploadOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { UserOutlined, ClockCircleOutlined, EyeOutlined, CheckCircleFilled, LeftOutlined, UploadOutlined, EditOutlined, StarFilled, CloseOutlined } from '@ant-design/icons';
|
||||
import { getTopicDetail, createReply, uploadMedia, getStarUsers } from '../api';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import LoginModal from '../components/LoginModal';
|
||||
@@ -44,10 +44,6 @@ const ForumDetail = () => {
|
||||
// Star Users State
|
||||
const [starUsers, setStarUsers] = useState([]);
|
||||
|
||||
// User Info Modal State
|
||||
const [userInfoModalVisible, setUserInfoModalVisible] = useState(false);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
|
||||
const fetchTopic = async () => {
|
||||
try {
|
||||
const res = await getTopicDetail(id);
|
||||
@@ -85,10 +81,16 @@ const ForumDetail = () => {
|
||||
message.info(`已添加 @${nickname}`);
|
||||
};
|
||||
|
||||
// Expert Info Modal
|
||||
const [expertModalVisible, setExpertModalVisible] = useState(false);
|
||||
const [selectedExpert, setSelectedExpert] = useState(null);
|
||||
|
||||
const showUserTitle = (author) => {
|
||||
if (author) {
|
||||
setSelectedUser(author);
|
||||
setUserInfoModalVisible(true);
|
||||
if (author?.is_star) {
|
||||
setSelectedExpert(author);
|
||||
setExpertModalVisible(true);
|
||||
} else if (author?.title) {
|
||||
message.info(author.title);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -231,13 +233,13 @@ const ForumDetail = () => {
|
||||
|
||||
<Space size={isMobile ? 'small' : 'large'} style={{ color: '#888', marginTop: 10, flexWrap: 'wrap' }}>
|
||||
<Space>
|
||||
<div onClick={() => showUserTitle(topic.author_info)} style={{ cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
src={topic.author_info?.avatar_url}
|
||||
icon={<UserOutlined />}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
/>
|
||||
</div>
|
||||
<Avatar
|
||||
src={topic.author_info?.avatar_url}
|
||||
icon={<UserOutlined />}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => showUserTitle(topic.author_info)}
|
||||
/>
|
||||
<span style={{ color: '#ccc', fontSize: isMobile ? 12 : 14 }}>{topic.author_info?.nickname}</span>
|
||||
{topic.is_verified_owner && (
|
||||
<Tooltip title="已验证购买过相关产品">
|
||||
@@ -303,13 +305,13 @@ const ForumDetail = () => {
|
||||
styles={{ body: { padding: isMobile ? '15px' : '24px' } }}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: isMobile ? 10 : 16 }}>
|
||||
<div onClick={() => showUserTitle(reply.author_info)} style={{ cursor: 'pointer', height: 'fit-content' }}>
|
||||
<Avatar
|
||||
src={reply.author_info?.avatar_url}
|
||||
icon={<UserOutlined />}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
/>
|
||||
</div>
|
||||
<Avatar
|
||||
src={reply.author_info?.avatar_url}
|
||||
icon={<UserOutlined />}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => showUserTitle(reply.author_info)}
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
|
||||
<Space size={isMobile ? 'small' : 'middle'} align="center">
|
||||
@@ -419,42 +421,52 @@ const ForumDetail = () => {
|
||||
onClose={() => setLoginModalVisible(false)}
|
||||
onLoginSuccess={() => {}}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={null}
|
||||
open={userInfoModalVisible}
|
||||
onCancel={() => setUserInfoModalVisible(false)}
|
||||
footer={null}
|
||||
centered
|
||||
width={300}
|
||||
styles={{ body: { textAlign: 'center', padding: '30px 20px' } }}
|
||||
>
|
||||
{selectedUser && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
size={80}
|
||||
src={selectedUser.avatar_url}
|
||||
icon={<UserOutlined />}
|
||||
style={{ marginBottom: 16, border: '2px solid #f0f0f0' }}
|
||||
/>
|
||||
<Title level={4} style={{ marginBottom: 4, marginTop: 0 }}>{selectedUser.nickname}</Title>
|
||||
|
||||
{(selectedUser.is_star || selectedUser.title) && (
|
||||
<Tag color="gold" style={{ fontSize: 14, padding: '4px 12px', marginTop: 8 }}>
|
||||
{selectedUser.title || '技术专家'}
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{selectedUser.is_verified_owner && (
|
||||
<div style={{ marginTop: 12, display: 'flex', alignItems: 'center', color: '#00b96b' }}>
|
||||
<CheckCircleFilled style={{ marginRight: 6 }} />
|
||||
<Text style={{ color: '#00b96b', fontSize: 12 }}>已认证购买用户</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={expertModalVisible}
|
||||
onCancel={() => setExpertModalVisible(false)}
|
||||
footer={null}
|
||||
title={<Space><StarFilled style={{ color: '#ffd700' }} /><span style={{ color: '#fff' }}>技术专家信息</span></Space>}
|
||||
styles={{
|
||||
content: { background: 'rgba(30, 30, 30, 0.95)', border: '1px solid rgba(255, 255, 255, 0.1)', backdropFilter: 'blur(10px)' },
|
||||
header: { background: 'transparent', borderBottom: '1px solid rgba(255, 255, 255, 0.1)' },
|
||||
body: { background: 'transparent' },
|
||||
mask: { backdropFilter: 'blur(4px)' }
|
||||
}}
|
||||
closeIcon={<CloseOutlined style={{ color: 'rgba(255, 255, 255, 0.5)' }} />}
|
||||
bodyStyle={{ textAlign: 'center' }}
|
||||
>
|
||||
{selectedExpert && (
|
||||
<div>
|
||||
<Avatar size={80} src={selectedExpert.avatar_url} icon={<UserOutlined />} style={{ border: '3px solid #ffd700', marginBottom: 15, boxShadow: '0 0 15px rgba(255, 215, 0, 0.2)' }} />
|
||||
<Title level={4} style={{ marginBottom: 5, color: '#fff' }}>{selectedExpert.nickname}</Title>
|
||||
<Tag color="gold" style={{ marginBottom: 20, background: 'rgba(255, 215, 0, 0.15)', border: '1px solid rgba(255, 215, 0, 0.4)', color: '#ffd700' }}>{selectedExpert.title || '技术专家'}</Tag>
|
||||
|
||||
{selectedExpert.skills && selectedExpert.skills.length > 0 && (
|
||||
<div style={{ marginTop: 20, textAlign: 'left' }}>
|
||||
<Text strong style={{ display: 'block', marginBottom: 10, color: '#00b96b' }}>擅长技能</Text>
|
||||
<Space wrap>
|
||||
{selectedExpert.skills.map((skill, idx) => (
|
||||
<div key={idx} style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
padding: '6px 12px',
|
||||
borderRadius: 6,
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
transition: 'all 0.3s'
|
||||
}}>
|
||||
{skill.icon && <img src={skill.icon} style={{ width: 16, height: 16, marginRight: 6 }} />}
|
||||
<Text style={{ fontSize: 12, color: '#ddd' }}>{skill.text}</Text>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Edit Modal */}
|
||||
<CreateTopicModal
|
||||
visible={editModalVisible}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Typography, Input, Button, List, Tag, Avatar, Card, Space, Spin, message, Badge, Tooltip, Tabs, Row, Col, Grid, Carousel } from 'antd';
|
||||
import { SearchOutlined, PlusOutlined, UserOutlined, MessageOutlined, EyeOutlined, CheckCircleFilled, FireOutlined, StarFilled, QuestionCircleOutlined, ShareAltOutlined, SoundOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Typography, Input, Button, List, Tag, Avatar, Card, Space, Spin, message, Badge, Tooltip, Tabs, Row, Col, Grid, Carousel, Modal } from 'antd';
|
||||
import { SearchOutlined, PlusOutlined, UserOutlined, MessageOutlined, EyeOutlined, CheckCircleFilled, FireOutlined, StarFilled, QuestionCircleOutlined, ShareAltOutlined, SoundOutlined, RightOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getTopics, getStarUsers, getAnnouncements } from '../api';
|
||||
@@ -25,6 +25,13 @@ const ForumList = () => {
|
||||
const [category, setCategory] = useState('all');
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
const [loginModalVisible, setLoginModalVisible] = useState(false);
|
||||
const [expertModalVisible, setExpertModalVisible] = useState(false);
|
||||
const [selectedExpert, setSelectedExpert] = useState(null);
|
||||
|
||||
const showExpertInfo = (user) => {
|
||||
setSelectedExpert(user);
|
||||
setExpertModalVisible(true);
|
||||
};
|
||||
|
||||
const fetchTopics = async (search = '', cat = '') => {
|
||||
setLoading(true);
|
||||
@@ -185,9 +192,9 @@ const ForumList = () => {
|
||||
{/* Mobile Experts */}
|
||||
<div style={{ overflowX: 'auto', whiteSpace: 'nowrap', paddingBottom: 5, display: 'flex', gap: 15, scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
{starUsers.map(u => (
|
||||
<div key={u.id} style={{ textAlign: 'center', minWidth: 60 }}>
|
||||
<div key={u.id} style={{ textAlign: 'center', minWidth: 60 }} onClick={() => showExpertInfo(u)}>
|
||||
<Badge count={<StarFilled style={{ color: '#ffd700' }} />} offset={[-5, 5]}>
|
||||
<Avatar size={48} src={u.avatar_url} icon={<UserOutlined />} style={{ border: '2px solid rgba(255, 215, 0, 0.3)' }} />
|
||||
<Avatar size={48} src={u.avatar_url} icon={<UserOutlined />} style={{ border: '2px solid rgba(255, 215, 0, 0.3)', cursor: 'pointer' }} />
|
||||
</Badge>
|
||||
<div style={{ color: '#fff', fontSize: 12, marginTop: 5, width: 60, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{u.nickname}
|
||||
@@ -307,7 +314,7 @@ const ForumList = () => {
|
||||
<div style={{ textAlign: 'center', padding: '20px 0', color: '#666' }}>
|
||||
{starUsers.length > 0 ? (
|
||||
starUsers.map(u => (
|
||||
<div key={u.id} style={{ marginBottom: 15, display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div key={u.id} style={{ marginBottom: 15, display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }} onClick={() => showExpertInfo(u)}>
|
||||
<Avatar size="large" src={u.avatar_url} icon={<UserOutlined />} />
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<div style={{ color: '#fff', fontWeight: 'bold' }}>
|
||||
@@ -370,6 +377,51 @@ const ForumList = () => {
|
||||
setCreateModalVisible(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={expertModalVisible}
|
||||
onCancel={() => setExpertModalVisible(false)}
|
||||
footer={null}
|
||||
title={<Space><StarFilled style={{ color: '#ffd700' }} /><span style={{ color: '#fff' }}>技术专家信息</span></Space>}
|
||||
styles={{
|
||||
content: { background: 'rgba(30, 30, 30, 0.95)', border: '1px solid rgba(255, 255, 255, 0.1)', backdropFilter: 'blur(10px)' },
|
||||
header: { background: 'transparent', borderBottom: '1px solid rgba(255, 255, 255, 0.1)' },
|
||||
body: { background: 'transparent' },
|
||||
mask: { backdropFilter: 'blur(4px)' }
|
||||
}}
|
||||
closeIcon={<CloseOutlined style={{ color: 'rgba(255, 255, 255, 0.5)' }} />}
|
||||
bodyStyle={{ textAlign: 'center' }}
|
||||
>
|
||||
{selectedExpert && (
|
||||
<div>
|
||||
<Avatar size={80} src={selectedExpert.avatar_url} icon={<UserOutlined />} style={{ border: '3px solid #ffd700', marginBottom: 15, boxShadow: '0 0 15px rgba(255, 215, 0, 0.2)' }} />
|
||||
<Title level={4} style={{ marginBottom: 5, color: '#fff' }}>{selectedExpert.nickname}</Title>
|
||||
<Tag color="gold" style={{ marginBottom: 20, background: 'rgba(255, 215, 0, 0.15)', border: '1px solid rgba(255, 215, 0, 0.4)', color: '#ffd700' }}>{selectedExpert.title || '技术专家'}</Tag>
|
||||
|
||||
{selectedExpert.skills && selectedExpert.skills.length > 0 && (
|
||||
<div style={{ marginTop: 20, textAlign: 'left' }}>
|
||||
<Text strong style={{ display: 'block', marginBottom: 10, color: '#00b96b' }}>擅长技能</Text>
|
||||
<Space wrap>
|
||||
{selectedExpert.skills.map((skill, idx) => (
|
||||
<div key={idx} style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
padding: '6px 12px',
|
||||
borderRadius: 6,
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
transition: 'all 0.3s'
|
||||
}}>
|
||||
{skill.icon && <img src={skill.icon} style={{ width: 16, height: 16, marginRight: 6 }} />}
|
||||
<Text style={{ fontSize: 12, color: '#ddd' }}>{skill.text}</Text>
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user