diff --git a/src/components/EventDetailPanel/CompactMetaBar.js b/src/components/EventDetailPanel/CompactMetaBar.js
index 3148ebf6..fa6f0f98 100644
--- a/src/components/EventDetailPanel/CompactMetaBar.js
+++ b/src/components/EventDetailPanel/CompactMetaBar.js
@@ -11,6 +11,7 @@ import {
} from '@chakra-ui/react';
import { EventFollowButton } from '@views/Community/components/EventCard/atoms';
import { Eye } from 'lucide-react';
+import ShareButton from '@components/ShareButton';
/**
* 精简信息栏组件
@@ -79,7 +80,7 @@ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggl
borderRadius="md"
boxShadow="sm"
>
-
+
{(event.view_count || 0).toLocaleString()}
@@ -93,6 +94,16 @@ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggl
size="sm"
showCount={true}
/>
+
+ {/* 分享按钮 */}
+
);
};
diff --git a/src/components/EventDetailPanel/EventHeaderInfo.js b/src/components/EventDetailPanel/EventHeaderInfo.js
index 3f787ef0..d3f9528c 100644
--- a/src/components/EventDetailPanel/EventHeaderInfo.js
+++ b/src/components/EventDetailPanel/EventHeaderInfo.js
@@ -16,6 +16,7 @@ import dayjs from 'dayjs';
import { Eye } from 'lucide-react';
import StockChangeIndicators from '../StockChangeIndicators';
import { EventFollowButton } from '@views/Community/components/EventCard/atoms';
+import ShareButton from '@components/ShareButton';
/**
* 事件头部信息区组件
@@ -70,27 +71,39 @@ const EventHeaderInfo = ({ event, importance, isFollowing, followerCount, onTogg
)}
- {/* 第一行:标题 + 关注按钮 */}
+ {/* 第一行:标题 + 关注按钮 + 分享按钮 */}
{/* 标题 */}
{event.title}
- {/* 关注按钮 */}
-
+
+ {/* 关注按钮 */}
+
+
+ {/* 分享按钮 */}
+
+
{/* 第二行:浏览数 + 日期 */}
{/* 浏览数 */}
-
+
{(event.view_count || 0).toLocaleString()}次浏览
diff --git a/src/components/EventDetailPanel/RelatedConceptsSection/TradingDateInfo.js b/src/components/EventDetailPanel/RelatedConceptsSection/TradingDateInfo.js
index 32d07641..d89cd6ed 100644
--- a/src/components/EventDetailPanel/RelatedConceptsSection/TradingDateInfo.js
+++ b/src/components/EventDetailPanel/RelatedConceptsSection/TradingDateInfo.js
@@ -5,6 +5,7 @@ import React from 'react';
import {
HStack,
Text,
+ Icon,
useColorModeValue,
} from '@chakra-ui/react';
import { Calendar } from 'lucide-react';
@@ -25,7 +26,7 @@ const TradingDateInfo = ({ effectiveTradingDate, eventTime }) => {
return (
-
+
涨跌幅数据:{effectiveTradingDate}
{eventTime && effectiveTradingDate !== dayjs(eventTime).format('YYYY-MM-DD') && (
diff --git a/src/components/EventDetailPanel/StockListItem.js b/src/components/EventDetailPanel/StockListItem.js
index acb863d1..7a38c882 100644
--- a/src/components/EventDetailPanel/StockListItem.js
+++ b/src/components/EventDetailPanel/StockListItem.js
@@ -14,6 +14,7 @@ import {
Collapse,
Tooltip,
Badge,
+ Icon,
useColorModeValue,
} from '@chakra-ui/react';
import { Tag } from 'antd';
@@ -197,7 +198,7 @@ const StockListItem = ({
size="xs"
variant={isInWatchlist ? 'solid' : 'outline'}
colorScheme={isInWatchlist ? 'yellow' : 'gray'}
- icon={}
+ icon={}
onClick={handleWatchlistClick}
aria-label={isInWatchlist ? '已关注' : '加自选'}
borderRadius="full"
diff --git a/src/components/ShareButton/index.js b/src/components/ShareButton/index.js
new file mode 100644
index 00000000..19ca7b54
--- /dev/null
+++ b/src/components/ShareButton/index.js
@@ -0,0 +1,252 @@
+// src/components/ShareButton/index.js
+// 分享按钮组件 - 支持微信分享引导和其他分享方式
+
+import React, { useState, useCallback } from 'react';
+import {
+ Button,
+ IconButton,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalCloseButton,
+ VStack,
+ HStack,
+ Text,
+ Box,
+ Icon,
+ useDisclosure,
+ useToast,
+ useColorModeValue,
+ Tooltip,
+} from '@chakra-ui/react';
+import { Share2, Copy, MessageCircle, Check } from 'lucide-react';
+import useWechatShare from '@hooks/useWechatShare';
+
+/**
+ * 分享按钮组件
+ *
+ * @param {Object} props
+ * @param {string} props.title - 分享标题
+ * @param {string} props.desc - 分享描述
+ * @param {string} props.link - 分享链接
+ * @param {string} props.imgUrl - 分享图片
+ * @param {string} props.size - 按钮大小 ('sm' | 'md' | 'lg')
+ * @param {string} props.variant - 按钮样式 ('solid' | 'outline' | 'ghost' | 'icon')
+ * @param {string} props.colorScheme - 颜色主题
+ * @param {React.ReactNode} props.children - 自定义按钮内容
+ */
+const ShareButton = ({
+ title = '',
+ desc = '',
+ link = '',
+ imgUrl = '',
+ size = 'sm',
+ variant = 'ghost',
+ colorScheme = 'gray',
+ children,
+ ...rest
+}) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const toast = useToast();
+ const [copied, setCopied] = useState(false);
+
+ // 主题颜色
+ const modalBg = useColorModeValue('white', 'gray.800');
+ const borderColor = useColorModeValue('gray.200', 'gray.600');
+ const hoverBg = useColorModeValue('gray.100', 'gray.700');
+
+ // 使用微信分享 Hook
+ const { isInWechat, triggerShare } = useWechatShare({
+ title,
+ desc,
+ link,
+ imgUrl,
+ autoSetup: true,
+ });
+
+ // 复制链接
+ const handleCopyLink = useCallback(async () => {
+ const shareLink = link || window.location.href;
+ try {
+ await navigator.clipboard.writeText(shareLink);
+ setCopied(true);
+ toast({
+ title: '链接已复制',
+ status: 'success',
+ duration: 2000,
+ isClosable: true,
+ });
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ // 降级方案:使用 execCommand
+ const textArea = document.createElement('textarea');
+ textArea.value = shareLink;
+ document.body.appendChild(textArea);
+ textArea.select();
+ document.execCommand('copy');
+ document.body.removeChild(textArea);
+ setCopied(true);
+ toast({
+ title: '链接已复制',
+ status: 'success',
+ duration: 2000,
+ isClosable: true,
+ });
+ setTimeout(() => setCopied(false), 2000);
+ }
+ }, [link, toast]);
+
+ // 点击分享按钮
+ const handleShareClick = useCallback(() => {
+ if (isInWechat) {
+ // 在微信中,打开分享引导弹窗
+ onOpen();
+ } else if (navigator.share) {
+ // 支持 Web Share API
+ navigator.share({
+ title: title || document.title,
+ text: desc,
+ url: link || window.location.href,
+ }).catch(() => {
+ // 用户取消或失败,打开弹窗作为降级
+ onOpen();
+ });
+ } else {
+ // 不支持 Web Share API,打开弹窗
+ onOpen();
+ }
+ }, [isInWechat, title, desc, link, onOpen]);
+
+ // 渲染按钮
+ const renderButton = () => {
+ if (children) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (variant === 'icon') {
+ return (
+
+ }
+ size={size}
+ variant="ghost"
+ colorScheme={colorScheme}
+ aria-label="分享"
+ onClick={handleShareClick}
+ {...rest}
+ />
+
+ );
+ }
+
+ return (
+ }
+ size={size}
+ variant={variant}
+ colorScheme={colorScheme}
+ onClick={handleShareClick}
+ {...rest}
+ >
+ 分享
+
+ );
+ };
+
+ return (
+ <>
+ {renderButton()}
+
+ {/* 分享引导弹窗 */}
+
+
+
+ 分享给好友
+
+
+
+ {/* 分享预览 */}
+
+
+ {title || '分享内容'}
+
+ {desc && (
+
+ {desc}
+
+ )}
+
+
+ {/* 分享选项 */}
+
+ {/* 微信分享提示(仅在微信环境显示) */}
+ {isInWechat && (
+
+
+
+
+ 微信分享
+
+
+ 请点击右上角「...」选择分享方式
+
+
+
+ )}
+
+ {/* 复制链接 */}
+
+
+
+
+ {copied ? '已复制' : '复制链接'}
+
+
+ {link || window.location.href}
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ShareButton;
diff --git a/src/hooks/useWechatShare.js b/src/hooks/useWechatShare.js
new file mode 100644
index 00000000..160cc2e6
--- /dev/null
+++ b/src/hooks/useWechatShare.js
@@ -0,0 +1,232 @@
+// src/hooks/useWechatShare.js
+// 微信分享 Hook - 支持自动配置右上角分享菜单和显式分享引导
+
+import { useState, useEffect, useCallback, useRef } from 'react';
+import { getJsSdkConfig } from '@services/miniprogramService';
+
+/**
+ * 检测是否在微信浏览器中
+ */
+const isWechatBrowser = () => {
+ const ua = navigator.userAgent.toLowerCase();
+ return ua.includes('micromessenger');
+};
+
+/**
+ * 微信分享 Hook
+ *
+ * @param {Object} options - 分享配置
+ * @param {string} options.title - 分享标题
+ * @param {string} options.desc - 分享描述
+ * @param {string} options.link - 分享链接(默认当前页面)
+ * @param {string} options.imgUrl - 分享图标 URL
+ * @param {boolean} options.autoSetup - 是否自动配置分享(默认 true)
+ *
+ * @returns {Object} 分享状态和方法
+ */
+const useWechatShare = (options = {}) => {
+ const {
+ title = '',
+ desc = '',
+ link = '',
+ imgUrl = '',
+ autoSetup = true,
+ } = options;
+
+ const [isReady, setIsReady] = useState(false);
+ const [isInWechat, setIsInWechat] = useState(false);
+ const [error, setError] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+
+ // 使用 ref 存储最新的分享配置,避免重复初始化
+ const shareConfigRef = useRef({ title, desc, link, imgUrl });
+ const isInitializedRef = useRef(false);
+
+ // 更新 ref 中的配置
+ useEffect(() => {
+ shareConfigRef.current = { title, desc, link, imgUrl };
+
+ // 如果已初始化且配置变化,更新分享内容
+ if (isReady && isInWechat && window.wx) {
+ updateShareData();
+ }
+ }, [title, desc, link, imgUrl, isReady, isInWechat]);
+
+ /**
+ * 更新分享数据
+ */
+ const updateShareData = useCallback(() => {
+ if (!window.wx || !isReady) return;
+
+ const config = shareConfigRef.current;
+ const shareLink = config.link || window.location.href.split('#')[0];
+
+ // 分享给朋友
+ window.wx.updateAppMessageShareData({
+ title: config.title || document.title,
+ desc: config.desc || '',
+ link: shareLink,
+ imgUrl: config.imgUrl || `${window.location.origin}/logo192.png`,
+ success: () => {
+ console.log('[WechatShare] updateAppMessageShareData 设置成功');
+ },
+ fail: (err) => {
+ console.error('[WechatShare] updateAppMessageShareData 失败:', err);
+ }
+ });
+
+ // 分享到朋友圈
+ window.wx.updateTimelineShareData({
+ title: config.title || document.title,
+ link: shareLink,
+ imgUrl: config.imgUrl || `${window.location.origin}/logo192.png`,
+ success: () => {
+ console.log('[WechatShare] updateTimelineShareData 设置成功');
+ },
+ fail: (err) => {
+ console.error('[WechatShare] updateTimelineShareData 失败:', err);
+ }
+ });
+ }, [isReady]);
+
+ /**
+ * 初始化微信 JS-SDK
+ */
+ const initWxSdk = useCallback(async () => {
+ if (isInitializedRef.current || !isInWechat) return;
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ // 获取当前页面 URL(不含 hash)
+ const currentUrl = window.location.href.split('#')[0];
+
+ // 获取 JS-SDK 签名配置
+ const config = await getJsSdkConfig(currentUrl);
+
+ if (!config) {
+ throw new Error('获取签名配置失败');
+ }
+
+ // 配置微信 JS-SDK
+ window.wx.config({
+ debug: false, // 生产环境关闭
+ appId: config.appId,
+ timestamp: config.timestamp,
+ nonceStr: config.nonceStr,
+ signature: config.signature,
+ jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'],
+ });
+
+ // 监听 ready 事件
+ window.wx.ready(() => {
+ console.log('[WechatShare] wx.ready 触发');
+ isInitializedRef.current = true;
+ setIsReady(true);
+ setIsLoading(false);
+
+ // 自动设置分享内容
+ if (autoSetup) {
+ updateShareData();
+ }
+ });
+
+ // 监听 error 事件
+ window.wx.error((err) => {
+ console.error('[WechatShare] wx.error:', err);
+ setError(err.errMsg || '微信 JS-SDK 初始化失败');
+ setIsLoading(false);
+ });
+
+ } catch (err) {
+ console.error('[WechatShare] 初始化失败:', err);
+ setError(err.message || '初始化失败');
+ setIsLoading(false);
+ }
+ }, [isInWechat, autoSetup, updateShareData]);
+
+ // 检测微信环境并初始化
+ useEffect(() => {
+ const inWechat = isWechatBrowser();
+ setIsInWechat(inWechat);
+
+ if (inWechat && autoSetup) {
+ // 确保 wx 对象已加载
+ if (window.wx) {
+ initWxSdk();
+ } else {
+ // 等待 wx 对象加载
+ const checkWx = setInterval(() => {
+ if (window.wx) {
+ clearInterval(checkWx);
+ initWxSdk();
+ }
+ }, 100);
+
+ // 3秒后停止检查
+ setTimeout(() => clearInterval(checkWx), 3000);
+ }
+ }
+ }, [autoSetup, initWxSdk]);
+
+ /**
+ * 手动触发分享引导
+ * 在微信中会提示用户点击右上角分享
+ * 在非微信环境中可以展示其他分享方式
+ */
+ const triggerShare = useCallback(() => {
+ if (!isInWechat) {
+ // 非微信环境,可以使用 Web Share API 或复制链接
+ if (navigator.share) {
+ navigator.share({
+ title: shareConfigRef.current.title || document.title,
+ text: shareConfigRef.current.desc,
+ url: shareConfigRef.current.link || window.location.href,
+ }).catch((err) => {
+ console.log('[WechatShare] Web Share 取消或失败:', err);
+ });
+ } else {
+ // 复制链接到剪贴板
+ const link = shareConfigRef.current.link || window.location.href;
+ navigator.clipboard?.writeText(link).then(() => {
+ console.log('[WechatShare] 链接已复制');
+ });
+ }
+ return { type: 'web', success: true };
+ }
+
+ // 微信环境:无法直接触发分享,返回提示信息
+ return {
+ type: 'wechat',
+ success: true,
+ message: '请点击右上角「...」进行分享',
+ };
+ }, [isInWechat]);
+
+ /**
+ * 手动更新分享配置
+ */
+ const setShareConfig = useCallback((newConfig) => {
+ shareConfigRef.current = {
+ ...shareConfigRef.current,
+ ...newConfig,
+ };
+
+ if (isReady && isInWechat) {
+ updateShareData();
+ }
+ }, [isReady, isInWechat, updateShareData]);
+
+ return {
+ isReady, // JS-SDK 是否就绪
+ isInWechat, // 是否在微信浏览器中
+ isLoading, // 是否正在加载
+ error, // 错误信息
+ triggerShare, // 触发分享引导
+ setShareConfig, // 手动更新分享配置
+ updateShareData, // 手动更新分享数据到微信
+ };
+};
+
+export default useWechatShare;
diff --git a/src/views/Community/components/EventCard/atoms/EventEngagement.js b/src/views/Community/components/EventCard/atoms/EventEngagement.js
index 29726c5e..bcc2ddfb 100644
--- a/src/views/Community/components/EventCard/atoms/EventEngagement.js
+++ b/src/views/Community/components/EventCard/atoms/EventEngagement.js
@@ -4,7 +4,6 @@
import React, { useState, useCallback } from 'react';
import {
HStack,
- VStack,
Box,
Text,
Tooltip,
@@ -41,7 +40,7 @@ const EventEngagement = ({
onVoteChange,
}) => {
const toast = useToast();
- const { isLoggedIn } = useAuth();
+ const { isAuthenticated: isLoggedIn } = useAuth();
const [localVote, setLocalVote] = useState(userVote);
const [localBullish, setLocalBullish] = useState(bullishCount);