添加用户的看多/看空逻辑
This commit is contained in:
@@ -252,8 +252,7 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 第二行:涨跌幅数据 + 互动指标 */}
|
{/* 第二行:涨跌幅数据 */}
|
||||||
<HStack justify="space-between" align="center" flexWrap="wrap" gap={1}>
|
|
||||||
<StockChangeIndicators
|
<StockChangeIndicators
|
||||||
maxChange={event.related_max_chg}
|
maxChange={event.related_max_chg}
|
||||||
avgChange={event.related_avg_chg}
|
avgChange={event.related_avg_chg}
|
||||||
@@ -261,8 +260,9 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
size={indicatorSize}
|
size={indicatorSize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 互动指标:浏览量、关注数、看多/看空 */}
|
{/* 第三行:互动指标 - 独立一行更醒目 */}
|
||||||
{showEngagement && (
|
{showEngagement && (
|
||||||
|
<Box mt={0.5}>
|
||||||
<EventEngagement
|
<EventEngagement
|
||||||
eventId={event.id}
|
eventId={event.id}
|
||||||
viewCount={event.view_count}
|
viewCount={event.view_count}
|
||||||
@@ -270,11 +270,11 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
bullishCount={event.bullish_count}
|
bullishCount={event.bullish_count}
|
||||||
bearishCount={event.bearish_count}
|
bearishCount={event.bearish_count}
|
||||||
userVote={event.user_vote}
|
userVote={event.user_vote}
|
||||||
size="xs"
|
size="sm"
|
||||||
onVoteChange={onVoteChange}
|
onVoteChange={onVoteChange}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
HStack,
|
HStack,
|
||||||
|
VStack,
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
IconButton,
|
|
||||||
Badge,
|
|
||||||
useToast,
|
useToast,
|
||||||
|
Flex,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ViewIcon, StarIcon } from '@chakra-ui/icons';
|
import { ViewIcon, StarIcon } from '@chakra-ui/icons';
|
||||||
import { TbArrowBigUp, TbArrowBigDown, TbArrowBigUpFilled, TbArrowBigDownFilled } from 'react-icons/tb';
|
import { TbArrowBigUp, TbArrowBigDown, TbArrowBigUpFilled, TbArrowBigDownFilled } from 'react-icons/tb';
|
||||||
@@ -18,8 +18,6 @@ import { useAuth } from '@contexts/AuthContext';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化数字(大于1000显示为 1k, 1.2k 等)
|
* 格式化数字(大于1000显示为 1k, 1.2k 等)
|
||||||
* @param {number} num - 原始数字
|
|
||||||
* @returns {string} 格式化后的字符串
|
|
||||||
*/
|
*/
|
||||||
const formatCount = (num) => {
|
const formatCount = (num) => {
|
||||||
if (num == null || isNaN(num)) return '0';
|
if (num == null || isNaN(num)) return '0';
|
||||||
@@ -29,17 +27,7 @@ const formatCount = (num) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件互动组件
|
* 事件互动组件 - 精美设计版
|
||||||
* @param {Object} props
|
|
||||||
* @param {number} props.eventId - 事件ID
|
|
||||||
* @param {number} props.viewCount - 浏览量
|
|
||||||
* @param {number} props.followerCount - 关注数
|
|
||||||
* @param {number} props.bullishCount - 看多数
|
|
||||||
* @param {number} props.bearishCount - 看空数
|
|
||||||
* @param {string} props.userVote - 用户当前投票状态 ('bullish' | 'bearish' | null)
|
|
||||||
* @param {string} props.size - 尺寸 ('xs' | 'sm' | 'md')
|
|
||||||
* @param {boolean} props.showVoting - 是否显示投票功能
|
|
||||||
* @param {Function} props.onVoteChange - 投票变化回调
|
|
||||||
*/
|
*/
|
||||||
const EventEngagement = ({
|
const EventEngagement = ({
|
||||||
eventId,
|
eventId,
|
||||||
@@ -55,7 +43,6 @@ const EventEngagement = ({
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { isLoggedIn } = useAuth();
|
const { isLoggedIn } = useAuth();
|
||||||
|
|
||||||
// 本地状态管理(乐观更新)
|
|
||||||
const [localVote, setLocalVote] = useState(userVote);
|
const [localVote, setLocalVote] = useState(userVote);
|
||||||
const [localBullish, setLocalBullish] = useState(bullishCount);
|
const [localBullish, setLocalBullish] = useState(bullishCount);
|
||||||
const [localBearish, setLocalBearish] = useState(bearishCount);
|
const [localBearish, setLocalBearish] = useState(bearishCount);
|
||||||
@@ -63,16 +50,12 @@ const EventEngagement = ({
|
|||||||
|
|
||||||
// 尺寸配置
|
// 尺寸配置
|
||||||
const sizeConfig = {
|
const sizeConfig = {
|
||||||
xs: { fontSize: '10px', iconSize: '12px', spacing: 1, btnSize: 'xs' },
|
xs: { fontSize: '10px', iconSize: 12, spacing: 1.5, pillPx: 2, pillPy: 0.5 },
|
||||||
sm: { fontSize: 'xs', iconSize: '14px', spacing: 1.5, btnSize: 'xs' },
|
sm: { fontSize: '11px', iconSize: 14, spacing: 2, pillPx: 2.5, pillPy: 1 },
|
||||||
md: { fontSize: 'sm', iconSize: '16px', spacing: 2, btnSize: 'sm' },
|
md: { fontSize: '12px', iconSize: 16, spacing: 2.5, pillPx: 3, pillPy: 1.5 },
|
||||||
};
|
};
|
||||||
const config = sizeConfig[size] || sizeConfig.sm;
|
const config = sizeConfig[size] || sizeConfig.sm;
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理投票
|
|
||||||
* @param {string} voteType - 'bullish' | 'bearish'
|
|
||||||
*/
|
|
||||||
const handleVote = useCallback(async (voteType) => {
|
const handleVote = useCallback(async (voteType) => {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
toast({
|
toast({
|
||||||
@@ -88,22 +71,16 @@ const EventEngagement = ({
|
|||||||
if (isVoting) return;
|
if (isVoting) return;
|
||||||
setIsVoting(true);
|
setIsVoting(true);
|
||||||
|
|
||||||
// 计算新的投票状态
|
|
||||||
const newVote = localVote === voteType ? null : voteType;
|
const newVote = localVote === voteType ? null : voteType;
|
||||||
const oldVote = localVote;
|
const oldVote = localVote;
|
||||||
|
|
||||||
// 乐观更新本地状态
|
|
||||||
setLocalVote(newVote);
|
setLocalVote(newVote);
|
||||||
|
|
||||||
// 更新计数
|
|
||||||
let newBullish = localBullish;
|
let newBullish = localBullish;
|
||||||
let newBearish = localBearish;
|
let newBearish = localBearish;
|
||||||
|
|
||||||
// 取消之前的投票
|
|
||||||
if (oldVote === 'bullish') newBullish--;
|
if (oldVote === 'bullish') newBullish--;
|
||||||
if (oldVote === 'bearish') newBearish--;
|
if (oldVote === 'bearish') newBearish--;
|
||||||
|
|
||||||
// 添加新投票
|
|
||||||
if (newVote === 'bullish') newBullish++;
|
if (newVote === 'bullish') newBullish++;
|
||||||
if (newVote === 'bearish') newBearish++;
|
if (newVote === 'bearish') newBearish++;
|
||||||
|
|
||||||
@@ -124,14 +101,12 @@ const EventEngagement = ({
|
|||||||
throw new Error(data.error || '投票失败');
|
throw new Error(data.error || '投票失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用服务器返回的准确数据
|
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
setLocalBullish(data.data.bullish_count ?? newBullish);
|
setLocalBullish(data.data.bullish_count ?? newBullish);
|
||||||
setLocalBearish(data.data.bearish_count ?? newBearish);
|
setLocalBearish(data.data.bearish_count ?? newBearish);
|
||||||
setLocalVote(data.data.user_vote);
|
setLocalVote(data.data.user_vote);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触发回调
|
|
||||||
onVoteChange?.({
|
onVoteChange?.({
|
||||||
eventId,
|
eventId,
|
||||||
userVote: data.data?.user_vote,
|
userVote: data.data?.user_vote,
|
||||||
@@ -141,7 +116,6 @@ const EventEngagement = ({
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('投票失败:', error);
|
console.error('投票失败:', error);
|
||||||
// 回滚状态
|
|
||||||
setLocalVote(oldVote);
|
setLocalVote(oldVote);
|
||||||
setLocalBullish(bullishCount);
|
setLocalBullish(bullishCount);
|
||||||
setLocalBearish(bearishCount);
|
setLocalBearish(bearishCount);
|
||||||
@@ -158,92 +132,158 @@ const EventEngagement = ({
|
|||||||
}
|
}
|
||||||
}, [eventId, isLoggedIn, isVoting, localVote, localBullish, localBearish, bullishCount, bearishCount, toast, onVoteChange]);
|
}, [eventId, isLoggedIn, isVoting, localVote, localBullish, localBearish, bullishCount, bearishCount, toast, onVoteChange]);
|
||||||
|
|
||||||
// 计算看多比例(用于显示)
|
|
||||||
const totalVotes = localBullish + localBearish;
|
const totalVotes = localBullish + localBearish;
|
||||||
const bullishRatio = totalVotes > 0 ? Math.round((localBullish / totalVotes) * 100) : 50;
|
const bullishRatio = totalVotes > 0 ? Math.round((localBullish / totalVotes) * 100) : 50;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack spacing={config.spacing} onClick={(e) => e.stopPropagation()}>
|
<HStack spacing={config.spacing} onClick={(e) => e.stopPropagation()}>
|
||||||
{/* 浏览量 */}
|
{/* 浏览量 - 精美胶囊样式 */}
|
||||||
<Tooltip label="浏览量" placement="top" hasArrow>
|
<Tooltip label="浏览量" placement="top" hasArrow>
|
||||||
<HStack spacing={0.5} color="gray.400">
|
<HStack
|
||||||
<ViewIcon boxSize={config.iconSize} />
|
spacing={1}
|
||||||
<Text fontSize={config.fontSize}>{formatCount(viewCount)}</Text>
|
bg="whiteAlpha.100"
|
||||||
|
px={config.pillPx}
|
||||||
|
py={config.pillPy}
|
||||||
|
borderRadius="full"
|
||||||
|
cursor="default"
|
||||||
|
_hover={{ bg: 'whiteAlpha.200' }}
|
||||||
|
transition="all 0.2s"
|
||||||
|
>
|
||||||
|
<ViewIcon boxSize={`${config.iconSize}px`} color="gray.400" />
|
||||||
|
<Text fontSize={config.fontSize} color="gray.300" fontWeight="medium">
|
||||||
|
{formatCount(viewCount)}
|
||||||
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 关注数 */}
|
{/* 关注数 - 带星星图标 */}
|
||||||
<Tooltip label="关注数" placement="top" hasArrow>
|
<Tooltip label="关注数" placement="top" hasArrow>
|
||||||
<HStack spacing={0.5} color="gray.400">
|
<HStack
|
||||||
<StarIcon boxSize={config.iconSize} />
|
spacing={1}
|
||||||
<Text fontSize={config.fontSize}>{formatCount(followerCount)}</Text>
|
bg="whiteAlpha.100"
|
||||||
|
px={config.pillPx}
|
||||||
|
py={config.pillPy}
|
||||||
|
borderRadius="full"
|
||||||
|
cursor="default"
|
||||||
|
_hover={{ bg: 'whiteAlpha.200' }}
|
||||||
|
transition="all 0.2s"
|
||||||
|
>
|
||||||
|
<StarIcon boxSize={`${config.iconSize - 2}px`} color="yellow.400" />
|
||||||
|
<Text fontSize={config.fontSize} color="gray.300" fontWeight="medium">
|
||||||
|
{formatCount(followerCount)}
|
||||||
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 看多/看空投票 */}
|
{/* 看多/看空投票区 - 精美设计 */}
|
||||||
{showVoting && (
|
{showVoting && (
|
||||||
<HStack spacing={0.5} ml={1}>
|
<HStack
|
||||||
|
spacing={0}
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
borderRadius="full"
|
||||||
|
overflow="hidden"
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
{/* 看多按钮 */}
|
{/* 看多按钮 */}
|
||||||
<Tooltip label={localVote === 'bullish' ? '取消看多' : '看多'} placement="top" hasArrow>
|
<Tooltip label={localVote === 'bullish' ? '取消看多' : '看多'} placement="top" hasArrow>
|
||||||
<IconButton
|
<Flex
|
||||||
size={config.btnSize}
|
as="button"
|
||||||
variant="ghost"
|
align="center"
|
||||||
colorScheme="red"
|
justify="center"
|
||||||
icon={localVote === 'bullish'
|
gap={0.5}
|
||||||
|
px={config.pillPx}
|
||||||
|
py={config.pillPy}
|
||||||
|
cursor="pointer"
|
||||||
|
bg={localVote === 'bullish' ? 'rgba(239, 68, 68, 0.25)' : 'transparent'}
|
||||||
|
color={localVote === 'bullish' ? 'red.400' : 'gray.400'}
|
||||||
|
_hover={{
|
||||||
|
bg: localVote === 'bullish' ? 'rgba(239, 68, 68, 0.35)' : 'rgba(239, 68, 68, 0.15)',
|
||||||
|
color: 'red.400',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
onClick={() => handleVote('bullish')}
|
||||||
|
disabled={isVoting}
|
||||||
|
borderRightWidth="1px"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
>
|
||||||
|
{localVote === 'bullish'
|
||||||
? <TbArrowBigUpFilled size={config.iconSize} />
|
? <TbArrowBigUpFilled size={config.iconSize} />
|
||||||
: <TbArrowBigUp size={config.iconSize} />
|
: <TbArrowBigUp size={config.iconSize} />
|
||||||
}
|
}
|
||||||
onClick={() => handleVote('bullish')}
|
<Text fontSize={config.fontSize} fontWeight="semibold">
|
||||||
isLoading={isVoting}
|
{formatCount(localBullish)}
|
||||||
aria-label="看多"
|
</Text>
|
||||||
minW="auto"
|
</Flex>
|
||||||
h="auto"
|
|
||||||
p={0.5}
|
|
||||||
color={localVote === 'bullish' ? 'red.400' : 'gray.400'}
|
|
||||||
_hover={{ color: 'red.400', bg: 'rgba(239, 68, 68, 0.1)' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 投票比例显示 */}
|
{/* 比例指示条 - 中间显示 */}
|
||||||
{totalVotes > 0 && (
|
{totalVotes > 0 && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={`看多 ${localBullish} | 看空 ${localBearish}`}
|
label={`看多 ${bullishRatio}% | 看空 ${100 - bullishRatio}%`}
|
||||||
placement="top"
|
placement="top"
|
||||||
hasArrow
|
hasArrow
|
||||||
>
|
>
|
||||||
<Badge
|
<Box
|
||||||
|
px={1.5}
|
||||||
|
py={config.pillPy}
|
||||||
|
bg="whiteAlpha.50"
|
||||||
|
position="relative"
|
||||||
|
minW="32px"
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
|
{/* 背景进度条 */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left={0}
|
||||||
|
top={0}
|
||||||
|
bottom={0}
|
||||||
|
width={`${bullishRatio}%`}
|
||||||
|
bg={bullishRatio >= 50 ? 'rgba(239, 68, 68, 0.2)' : 'rgba(16, 185, 129, 0.2)'}
|
||||||
|
transition="width 0.3s ease"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
fontSize={config.fontSize}
|
fontSize={config.fontSize}
|
||||||
px={1}
|
fontWeight="bold"
|
||||||
py={0}
|
|
||||||
borderRadius="sm"
|
|
||||||
bg={bullishRatio >= 50 ? 'rgba(239, 68, 68, 0.15)' : 'rgba(16, 185, 129, 0.15)'}
|
|
||||||
color={bullishRatio >= 50 ? 'red.400' : 'green.400'}
|
color={bullishRatio >= 50 ? 'red.400' : 'green.400'}
|
||||||
fontWeight="medium"
|
position="relative"
|
||||||
|
zIndex={1}
|
||||||
>
|
>
|
||||||
{bullishRatio}%
|
{bullishRatio}%
|
||||||
</Badge>
|
</Text>
|
||||||
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 看空按钮 */}
|
{/* 看空按钮 */}
|
||||||
<Tooltip label={localVote === 'bearish' ? '取消看空' : '看空'} placement="top" hasArrow>
|
<Tooltip label={localVote === 'bearish' ? '取消看空' : '看空'} placement="top" hasArrow>
|
||||||
<IconButton
|
<Flex
|
||||||
size={config.btnSize}
|
as="button"
|
||||||
variant="ghost"
|
align="center"
|
||||||
colorScheme="green"
|
justify="center"
|
||||||
icon={localVote === 'bearish'
|
gap={0.5}
|
||||||
|
px={config.pillPx}
|
||||||
|
py={config.pillPy}
|
||||||
|
cursor="pointer"
|
||||||
|
bg={localVote === 'bearish' ? 'rgba(16, 185, 129, 0.25)' : 'transparent'}
|
||||||
|
color={localVote === 'bearish' ? 'green.400' : 'gray.400'}
|
||||||
|
_hover={{
|
||||||
|
bg: localVote === 'bearish' ? 'rgba(16, 185, 129, 0.35)' : 'rgba(16, 185, 129, 0.15)',
|
||||||
|
color: 'green.400',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
onClick={() => handleVote('bearish')}
|
||||||
|
disabled={isVoting}
|
||||||
|
borderLeftWidth={totalVotes > 0 ? '1px' : 0}
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
>
|
||||||
|
{localVote === 'bearish'
|
||||||
? <TbArrowBigDownFilled size={config.iconSize} />
|
? <TbArrowBigDownFilled size={config.iconSize} />
|
||||||
: <TbArrowBigDown size={config.iconSize} />
|
: <TbArrowBigDown size={config.iconSize} />
|
||||||
}
|
}
|
||||||
onClick={() => handleVote('bearish')}
|
<Text fontSize={config.fontSize} fontWeight="semibold">
|
||||||
isLoading={isVoting}
|
{formatCount(localBearish)}
|
||||||
aria-label="看空"
|
</Text>
|
||||||
minW="auto"
|
</Flex>
|
||||||
h="auto"
|
|
||||||
p={0.5}
|
|
||||||
color={localVote === 'bearish' ? 'green.400' : 'gray.400'}
|
|
||||||
_hover={{ color: 'green.400', bg: 'rgba(16, 185, 129, 0.1)' }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user