From 4912105a8de7c134000e67bf6a2c0b702cf547df Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Tue, 6 Jan 2026 12:09:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E8=82=A1=E8=AE=BA=E5=9D=9B=E9=87=8D?= =?UTF-8?q?=E5=81=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- add_prediction_channel.sql | 16 - .../components/ChannelSidebar/index.tsx | 6 +- .../MessageArea/PredictionChannel/index.tsx | 433 ---------------- .../components/MessageArea/index.tsx | 8 - .../PredictionTopicCardDark.tsx | 0 .../components/PredictionMarket/index.tsx | 474 ++++++++++++++++++ src/views/StockCommunity/index.tsx | 252 +++++++--- src/views/StockCommunity/types/index.ts | 2 +- 8 files changed, 656 insertions(+), 535 deletions(-) delete mode 100644 add_prediction_channel.sql delete mode 100644 src/views/StockCommunity/components/MessageArea/PredictionChannel/index.tsx rename src/views/StockCommunity/components/{MessageArea/PredictionChannel => PredictionMarket}/PredictionTopicCardDark.tsx (100%) create mode 100644 src/views/StockCommunity/components/PredictionMarket/index.tsx diff --git a/add_prediction_channel.sql b/add_prediction_channel.sql deleted file mode 100644 index fe022f8f..00000000 --- a/add_prediction_channel.sql +++ /dev/null @@ -1,16 +0,0 @@ --- 添加预测市场频道到社区 --- 在 MySQL 中执行 - --- 首先检查是否已存在预测市场分类 -INSERT IGNORE INTO community_categories (id, name, icon, position, is_collapsible, is_system, created_at, updated_at) -VALUES ('cat_prediction', '预测市场', '⚡', 2, 1, 1, NOW(), NOW()); - --- 添加预测市场频道 -INSERT IGNORE INTO community_channels -(id, category_id, name, type, topic, position, slow_mode, is_readonly, is_visible, is_system, subscriber_count, message_count, created_at, updated_at) -VALUES -('ch_prediction', 'cat_prediction', '预测大厅', 'prediction', 'Polymarket 风格预测市场,用积分参与预测,赢取奖池', 1, 0, 0, 1, 1, 0, 0, NOW(), NOW()); - --- 查看结果 -SELECT * FROM community_categories WHERE id = 'cat_prediction'; -SELECT * FROM community_channels WHERE type = 'prediction'; diff --git a/src/views/StockCommunity/components/ChannelSidebar/index.tsx b/src/views/StockCommunity/components/ChannelSidebar/index.tsx index 164c3e91..83d1e48c 100644 --- a/src/views/StockCommunity/components/ChannelSidebar/index.tsx +++ b/src/views/StockCommunity/components/ChannelSidebar/index.tsx @@ -48,7 +48,7 @@ import { Star, Settings, Users, - Zap, + Flame, } from 'lucide-react'; import { Channel, ChannelCategory, ChannelType } from '../../types'; @@ -67,7 +67,7 @@ const channelIcons: Record = { text: MessageSquare, forum: FileText, announcement: Megaphone, - prediction: Zap, + voice: Users, }; // 动画配置 @@ -281,7 +281,7 @@ const ChannelSidebar: React.FC = ({ {/* 热门标记 */} {channel.isHot && ( - + )} {/* 概念频道股票数 */} diff --git a/src/views/StockCommunity/components/MessageArea/PredictionChannel/index.tsx b/src/views/StockCommunity/components/MessageArea/PredictionChannel/index.tsx deleted file mode 100644 index b0194501..00000000 --- a/src/views/StockCommunity/components/MessageArea/PredictionChannel/index.tsx +++ /dev/null @@ -1,433 +0,0 @@ -/** - * 预测市场频道组件 - HeroUI 深色风格 - * Polymarket 风格的预测市场 - */ -import React, { useState, useEffect, useCallback } from 'react'; -import { - Box, - Flex, - Text, - Button, - Icon, - IconButton, - HStack, - VStack, - Select, - Spinner, - useDisclosure, - Badge, - Tooltip, - SimpleGrid, -} from '@chakra-ui/react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { - Plus, - TrendingUp, - Filter, - Zap, - Trophy, - HelpCircle, - Coins, - RefreshCw, -} from 'lucide-react'; -import { useNavigate } from 'react-router-dom'; - -import { Channel } from '../../../types'; -import { GLASS_BLUR } from '@/constants/glassConfig'; -import { useAuth } from '@/contexts/AuthContext'; -import { getTopics, getUserAccount } from '@services/predictionMarketService.api'; - -// 导入原有组件 -import CreatePredictionModal from '@views/ValueForum/components/CreatePredictionModal'; -import PredictionGuideModal from '@views/ValueForum/components/PredictionGuideModal'; -import PredictionTopicCardDark from './PredictionTopicCardDark'; - -interface PredictionChannelProps { - channel: Channel; -} - -type SortOption = 'latest' | 'hot' | 'ending_soon' | 'highest_pool'; -type FilterOption = 'all' | 'active' | 'settled'; - -const PredictionChannel: React.FC = ({ channel }) => { - const navigate = useNavigate(); - const { user, isAuthenticated } = useAuth(); - - const [topics, setTopics] = useState([]); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); - const [sortBy, setSortBy] = useState('latest'); - const [filterBy, setFilterBy] = useState('active'); - const [userAccount, setUserAccount] = useState(null); - - const { - isOpen: isCreateOpen, - onOpen: onCreateOpen, - onClose: onCreateClose, - } = useDisclosure(); - - const { - isOpen: isGuideOpen, - onOpen: onGuideOpen, - onClose: onGuideClose, - } = useDisclosure(); - - // 加载话题列表 - const loadTopics = useCallback(async (showRefreshing = false) => { - try { - if (showRefreshing) { - setRefreshing(true); - } else { - setLoading(true); - } - - const response = await getTopics({ - status: filterBy === 'all' ? undefined : filterBy, - sort: sortBy, - page: 1, - pageSize: 20, - }); - - if (response.success) { - setTopics(response.data.items || []); - } - } catch (error) { - console.error('加载预测话题失败:', error); - } finally { - setLoading(false); - setRefreshing(false); - } - }, [sortBy, filterBy]); - - // 加载用户账户 - const loadUserAccount = useCallback(async () => { - if (!isAuthenticated) return; - try { - const response = await getUserAccount(); - if (response.success) { - setUserAccount(response.data); - } - } catch (error) { - console.error('加载用户账户失败:', error); - } - }, [isAuthenticated]); - - useEffect(() => { - loadTopics(); - }, [loadTopics]); - - useEffect(() => { - loadUserAccount(); - }, [loadUserAccount]); - - // 创建成功回调 - const handleTopicCreated = (newTopic: any) => { - setTopics(prev => [newTopic, ...prev]); - onCreateClose(); - loadUserAccount(); // 刷新余额 - }; - - // 点击话题卡片 - const handleTopicClick = (topicId: string) => { - navigate(`/value-forum/prediction/${topicId}`); - }; - - return ( - - {/* 频道头部 */} - - - - - - - - - - {channel.name} - - - 预测市场 - - - - 类似 Polymarket 的预测交易市场 - - - - - - {/* 用户积分 */} - {isAuthenticated && userAccount && ( - - - - - {userAccount.balance?.toLocaleString()} - - - - )} - - {/* 玩法说明 */} - - } - variant="ghost" - size="sm" - color="gray.400" - _hover={{ bg: 'whiteAlpha.100', color: 'white' }} - onClick={onGuideOpen} - /> - - - {/* 排行榜入口 */} - - } - variant="ghost" - size="sm" - color="gray.400" - _hover={{ bg: 'whiteAlpha.100', color: 'yellow.400' }} - onClick={() => navigate('/value-forum/my-points')} - /> - - - - - - {/* 工具栏 */} - - - - - - - {/* 刷新按钮 */} - } - variant="ghost" - size="sm" - color="gray.400" - _hover={{ bg: 'whiteAlpha.100', color: 'white' }} - onClick={() => loadTopics(true)} - isDisabled={refreshing} - /> - - - - {/* 状态筛选 */} - - - {/* 排序 */} - - - - - - - - {/* 话题列表 */} - - {loading ? ( - - - - 加载预测话题中... - - - ) : topics.length === 0 ? ( - - - - - - 暂无预测话题 - - 成为第一个发起预测的人! - - - - - - - ) : ( - - - {topics.map((topic, index) => ( - - handleTopicClick(topic.id)} - /> - - ))} - - - )} - - - {/* 创建话题弹窗 */} - - - {/* 玩法说明弹窗 */} - - - ); -}; - -export default PredictionChannel; diff --git a/src/views/StockCommunity/components/MessageArea/index.tsx b/src/views/StockCommunity/components/MessageArea/index.tsx index 0a890905..c2bc5c2f 100644 --- a/src/views/StockCommunity/components/MessageArea/index.tsx +++ b/src/views/StockCommunity/components/MessageArea/index.tsx @@ -17,7 +17,6 @@ import { MessageSquare, Hash, Users } from 'lucide-react'; import { Channel } from '../../types'; import TextChannel from './TextChannel'; import ForumChannel from './ForumChannel'; -import PredictionChannel from './PredictionChannel'; import { GLASS_BLUR } from '@/constants/glassConfig'; interface MessageAreaProps { @@ -154,13 +153,6 @@ const MessageArea: React.FC = ({ /> ); - case 'prediction': - return ( - - ); - case 'announcement': return ( { + const navigate = useNavigate(); + const { user, isAuthenticated } = useAuth(); + + const [topics, setTopics] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [sortBy, setSortBy] = useState('latest'); + const [filterBy, setFilterBy] = useState('active'); + const [searchKeyword, setSearchKeyword] = useState(''); + const [userAccount, setUserAccount] = useState(null); + + const { + isOpen: isCreateOpen, + onOpen: onCreateOpen, + onClose: onCreateClose, + } = useDisclosure(); + + const { + isOpen: isGuideOpen, + onOpen: onGuideOpen, + onClose: onGuideClose, + } = useDisclosure(); + + // 加载话题列表 + const loadTopics = useCallback(async (showRefreshing = false) => { + try { + if (showRefreshing) { + setRefreshing(true); + } else { + setLoading(true); + } + + const response = await getTopics({ + status: filterBy === 'all' ? undefined : filterBy, + sort: sortBy, + page: 1, + pageSize: 30, + }); + + if (response.success) { + let items = response.data.items || response.data || []; + + // 前端搜索过滤 + if (searchKeyword.trim()) { + const keyword = searchKeyword.toLowerCase(); + items = items.filter((topic: any) => + topic.title?.toLowerCase().includes(keyword) || + topic.description?.toLowerCase().includes(keyword) + ); + } + + setTopics(items); + } + } catch (error) { + console.error('加载预测话题失败:', error); + } finally { + setLoading(false); + setRefreshing(false); + } + }, [sortBy, filterBy, searchKeyword]); + + // 加载用户账户 + const loadUserAccount = useCallback(async () => { + if (!isAuthenticated) return; + try { + const response = await getUserAccount(); + if (response.success) { + setUserAccount(response.data); + } + } catch (error) { + console.error('加载用户账户失败:', error); + } + }, [isAuthenticated]); + + useEffect(() => { + loadTopics(); + }, [loadTopics]); + + useEffect(() => { + loadUserAccount(); + }, [loadUserAccount]); + + // 搜索处理 + const handleSearch = () => { + loadTopics(); + }; + + // 创建成功回调 + const handleTopicCreated = (newTopic: any) => { + setTopics(prev => [newTopic, ...prev]); + onCreateClose(); + loadUserAccount(); // 刷新余额 + }; + + // 点击话题卡片 + const handleTopicClick = (topicId: string) => { + navigate(`/value-forum/prediction/${topicId}`); + }; + + // 排序选项配置 + const sortOptions = [ + { value: 'latest', label: '最新发布', icon: Clock }, + { value: 'hot', label: '最热门', icon: Flame }, + { value: 'ending_soon', label: '即将截止', icon: Clock }, + { value: 'highest_pool', label: '奖池最高', icon: Coins }, + ]; + + return ( + + {/* 顶部区域 */} + + {/* 标题和操作栏 */} + + + + + + + + 预测市场 + + + BETA + + + + 类似 Polymarket 的预测交易市场,用积分参与预测,赢取奖池 + + + + + {/* 用户积分 */} + {isAuthenticated && userAccount && ( + + navigate('/value-forum/my-points')} + > + + + {userAccount.balance?.toLocaleString()} + + + + )} + + {/* 玩法说明 */} + + } + variant="outline" + borderColor="rgba(255, 255, 255, 0.2)" + color="gray.300" + _hover={{ bg: 'whiteAlpha.100', color: 'white', borderColor: 'yellow.400' }} + onClick={onGuideOpen} + /> + + + {/* 排行榜 */} + + } + variant="outline" + borderColor="rgba(255, 255, 255, 0.2)" + color="gray.300" + _hover={{ bg: 'whiteAlpha.100', color: 'yellow.400', borderColor: 'yellow.400' }} + onClick={() => navigate('/value-forum/my-points')} + /> + + + {/* 发起预测按钮 */} + + + + + + + {/* 搜索和筛选栏 */} + + {/* 搜索框 */} + + + + + setSearchKeyword(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSearch()} + bg="rgba(255, 255, 255, 0.05)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + color="white" + _placeholder={{ color: 'gray.500' }} + _hover={{ borderColor: 'rgba(255, 255, 255, 0.2)' }} + _focus={{ + borderColor: 'yellow.400', + boxShadow: '0 0 0 1px var(--chakra-colors-yellow-400)', + }} + /> + + + {/* 状态筛选 */} + + + {/* 排序选项按钮组 */} + + {sortOptions.map((option) => { + const IconComp = option.icon; + const isActive = sortBy === option.value; + + return ( + + ); + })} + + + {/* 刷新按钮 */} + } + variant="ghost" + color="gray.400" + _hover={{ bg: 'whiteAlpha.100', color: 'white' }} + onClick={() => loadTopics(true)} + isDisabled={refreshing} + /> + + + + {/* 话题列表 */} + + {loading ? ( + + + + 加载预测话题中... + + + ) : topics.length === 0 ? ( + + + + + + + {searchKeyword ? '未找到相关预测话题' : '暂无预测话题'} + + + {searchKeyword ? '换个关键词试试?' : '成为第一个发起预测的人!'} + + {!searchKeyword && ( + + + + )} + + + ) : ( + + + {topics.map((topic, index) => ( + + handleTopicClick(topic.id)} + /> + + ))} + + + )} + + + {/* 创建话题弹窗 */} + + + {/* 玩法说明弹窗 */} + + + ); +}; + +export default PredictionMarket; diff --git a/src/views/StockCommunity/index.tsx b/src/views/StockCommunity/index.tsx index fe91ead5..f7f5014c 100644 --- a/src/views/StockCommunity/index.tsx +++ b/src/views/StockCommunity/index.tsx @@ -1,6 +1,7 @@ /** * 股票社区主页面 - HeroUI 深色风格 * Discord 风格三栏布局:频道列表 | 消息区域 | 右侧面板 + * 支持 Tab 切换:社区频道 / 预测市场 */ import React, { useState, useEffect, useCallback } from 'react'; import { @@ -16,14 +17,21 @@ import { HStack, Icon, Tooltip, + Tabs, + TabList, + Tab, + TabPanels, + TabPanel, + Badge, } from '@chakra-ui/react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Menu, Users, Hash, Settings, Bell, Search } from 'lucide-react'; +import { Menu, Users, Hash, Settings, Bell, Search, MessageSquare, Zap } from 'lucide-react'; import { useSearchParams } from 'react-router-dom'; import ChannelSidebar from './components/ChannelSidebar'; import MessageArea from './components/MessageArea'; import RightPanel from './components/RightPanel'; +import PredictionMarket from './components/PredictionMarket'; import { useCommunitySocket } from './hooks/useCommunitySocket'; import { Channel } from './types'; import { GLASS_BLUR, GLASS_BG, GLASS_BORDER, GLASS_SHADOW } from '@/constants/glassConfig'; @@ -39,6 +47,7 @@ const StockCommunity: React.FC = () => { const [searchParams, setSearchParams] = useSearchParams(); const [activeChannel, setActiveChannel] = useState(null); const [rightPanelContent, setRightPanelContent] = useState<'members' | 'thread' | 'info'>('members'); + const [activeTab, setActiveTab] = useState(0); // 0: 社区频道, 1: 预测市场 // 移动端抽屉 const { isOpen: isLeftOpen, onOpen: onLeftOpen, onClose: onLeftClose } = useDisclosure(); @@ -98,82 +107,13 @@ const StockCommunity: React.FC = () => { /> ); - return ( + // 渲染社区频道视图(原有的 Discord 风格三栏布局) + const renderCommunityView = () => ( - {/* 背景装饰 */} - - - - {/* 移动端顶部操作栏 */} - {isMobile && ( - - } - variant="ghost" - color="gray.300" - _hover={{ bg: 'whiteAlpha.100', color: 'white' }} - onClick={onLeftOpen} - /> - - - - {activeChannel?.name || '股票社区'} - - - } - variant="ghost" - color="gray.300" - _hover={{ bg: 'whiteAlpha.100', color: 'white' }} - onClick={onRightOpen} - /> - - )} - {/* 左侧频道列表 - 桌面端 */} {showLeftSidebar && ( @@ -219,7 +159,6 @@ const StockCommunity: React.FC = () => { { ); + + return ( + + {/* 背景装饰 */} + + + + {/* Tabs 容器 */} + setActiveTab(index)} + isLazy + h="full" + display="flex" + flexDirection="column" + > + {/* Tab 切换栏 */} + + + {/* 移动端菜单按钮 */} + {isMobile && activeTab === 0 && ( + } + variant="ghost" + color="gray.300" + size="sm" + mr={2} + _hover={{ bg: 'whiteAlpha.100', color: 'white' }} + onClick={onLeftOpen} + /> + )} + + + + + + 社区频道 + + + + + + 预测市场 + + BETA + + + + + + {/* 移动端右侧按钮 */} + {isMobile && activeTab === 0 && ( + } + variant="ghost" + color="gray.300" + size="sm" + ml={2} + _hover={{ bg: 'whiteAlpha.100', color: 'white' }} + onClick={onRightOpen} + /> + )} + + + + {/* Tab 内容区域 */} + + {/* 社区频道 */} + + {renderCommunityView()} + + + {/* 预测市场 */} + + + + + + + ); }; export default StockCommunity; diff --git a/src/views/StockCommunity/types/index.ts b/src/views/StockCommunity/types/index.ts index 78b98f25..2a4546fe 100644 --- a/src/views/StockCommunity/types/index.ts +++ b/src/views/StockCommunity/types/index.ts @@ -6,7 +6,7 @@ // 频道相关 // ============================================================ -export type ChannelType = 'text' | 'forum' | 'announcement' | 'prediction'; +export type ChannelType = 'text' | 'forum' | 'announcement' | 'voice'; export interface ChannelCategory { id: string;