Compare commits

...

5 Commits

Author SHA1 Message Date
zdl
b1d5b217d3 refactor: 事件详情弹窗改用 Drawer 组件从底部弹出
- EventDetailModal: Modal 替换为 Drawer,placement="bottom"
- 使用 destroyOnHidden 替代已弃用的 destroyOnClose
- 关闭按钮改用 CloseOutlined 图标,移到右上角
- 简化 Less 文件,删除与 TSX styles 属性重复的配置
- BytedeskWidget: H5 端降低 z-index,避免遮挡发布按钮

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:45:48 +08:00
zdl
5f6b933172 fix: 在 viewport meta 标签中添加了 viewport-fit=cover,这样浏览器会将页面内容延伸到曲面屏边缘,同时启用 safe-area-inset-* CSS 环境变量
在普通设备上保持至少 16px 的右侧内边距
在华为 MATE70 PRO 等曲面屏设备上,使用系统提供的安全区域值,避免右侧导航被遮挡
2025-12-04 14:53:17 +08:00
zdl
0c291de182 fix: 概念中心H5端卡片尺寸优化,一屏可显示更多内容
- H5端改为两列布局,间距从6改为3
    - 卡片背景高度从180px减小到100px
    - Logo尺寸从120px减小到60px
    - 内容区域padding和间距响应式调整
    - 描述文字H5端显示1行
    - 时间轴按钮尺寸H5端缩小
2025-12-04 14:47:36 +08:00
zdl
61ed1510c2 fix: 修复自选股添加失败 405 错误
- useWatchlist.js: 修正 API 路径从 /api/account/watchlist/add 改为 /api/account/watchlist
- account.js: 同步修改 mock handler 路径

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 14:40:35 +08:00
zdl
0edc6a5e00 fix: H5端热门事件移除Tooltip避免黑色悬浮框无法消除
- 使用 useBreakpointValue 检测移动端设备
- H5端不显示标题和描述的 Tooltip 提示
- PC端保留 Tooltip 功能不变

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 14:38:14 +08:00
9 changed files with 77 additions and 71 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />

View File

@@ -75,9 +75,11 @@ const BytedeskWidget = ({
const rightVal = parseInt(style.right);
const bottomVal = parseInt(style.bottom);
if (rightVal >= 0 && rightVal < 100 && bottomVal >= 0 && bottomVal < 100) {
// H5 端设置按钮尺寸为 48x48只执行一次
// H5 端设置按钮尺寸为 48x48 并降低 z-index(只执行一次)
if (isMobile && !el.dataset.bytedeskStyled) {
el.dataset.bytedeskStyled = 'true';
// 降低 z-index避免遮挡页面内的发布按钮等交互元素
el.style.zIndex = 10;
const button = el.querySelector('button');
if (button) {
button.style.width = '48px';

View File

@@ -161,7 +161,7 @@ export default function HomeNavbar() {
borderColor={navbarBorder}
py={{ base: 2, md: 3 }}
>
<Container maxW="container.xl" px={{ base: 3, md: 4 }}>
<Container maxW="container.xl" px={{ base: 3, md: 4 }} style={{ paddingRight: 'max(16px, env(safe-area-inset-right))' }}>
<Flex justify="space-between" align="center">
{/* Logo - 价小前投研 */}
<BrandLogo />

View File

@@ -64,7 +64,7 @@ export const useWatchlist = () => {
const handleAddToWatchlist = useCallback(async (stockCode, stockName) => {
try {
const base = getApiBase();
const resp = await fetch(base + '/api/account/watchlist/add', {
const resp = await fetch(base + '/api/account/watchlist', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },

View File

@@ -159,7 +159,7 @@ export const accountHandlers = [
}),
// 6. 添加自选股
http.post('/api/account/watchlist/add', async ({ request }) => {
http.post('/api/account/watchlist', async ({ request }) => {
await delay(NETWORK_DELAY);
const currentUser = getCurrentUser();

View File

@@ -1,36 +1,8 @@
.event-detail-modal {
top: 20% !important;
margin: 0 auto !important;
padding-bottom: 0 !important;
.ant-modal-content {
border-radius: 24px !important;
background: transparent;
}
// 标题样式 - 深色文字(白色背景)
.ant-modal-title {
// 事件详情抽屉样式(从底部弹出)
// 注意:大部分样式已在 TSX 的 styles 属性中配置,这里只保留必要的覆盖
.event-detail-drawer {
// 标题样式
.ant-drawer-title {
color: #1A202C;
}
// 关闭按钮样式 - 深色(白色背景)
.ant-modal-close {
color: #4A5568;
&:hover {
color: #1A202C;
}
}
}
// 自底向上滑入动画
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Modal } from 'antd';
import { Drawer } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { selectIsMobile } from '@store/slices/deviceSlice';
import DynamicNewsDetailPanel from './DynamicNewsDetail/DynamicNewsDetailPanel';
import './EventDetailModal.less';
@@ -15,7 +16,7 @@ interface EventDetailModalProps {
}
/**
* 事件详情弹窗组件
* 事件详情抽屉组件(从底部弹出)
*/
const EventDetailModal: React.FC<EventDetailModalProps> = ({
open,
@@ -25,23 +26,35 @@ const EventDetailModal: React.FC<EventDetailModalProps> = ({
const isMobile = useSelector(selectIsMobile);
return (
<Modal
<Drawer
open={open}
onCancel={onClose}
footer={null}
onClose={onClose}
placement="bottom"
height={isMobile ? 'calc(100vh - 60px)' : 'calc(100vh - 100px)'}
width={isMobile ? '100%' : '70vw'}
title={event?.title || '事件详情'}
width='100vw'
destroyOnClose
className="event-detail-modal"
destroyOnHidden
rootClassName="event-detail-drawer"
closeIcon={null}
extra={
<CloseOutlined
onClick={onClose}
style={{ cursor: 'pointer', fontSize: 16, color: '#4A5568' }}
/>
}
styles={{
mask: { background: 'transparent' },
content: { borderRadius: 24, padding: 0, maxWidth: 1400, background: 'transparent', margin: '0 auto', maxHeight: '80vh', display: 'flex', flexDirection: 'column' },
header: { background: '#FFFFFF', borderBottom: '1px solid #E2E8F0', padding: '16px 24px', borderRadius: '24px 24px 0 0', margin: 0, flexShrink: 0 },
body: { padding: 0, overflowY: 'auto', flex: 1 },
wrapper: isMobile ? {} : {
maxWidth: 1400,
margin: '0 auto',
borderRadius: '16px 16px 0 0',
},
content: { borderRadius: '16px 16px 0 0' },
header: { background: '#FFFFFF', borderBottom: '1px solid #E2E8F0', padding: '16px 24px' },
body: { padding: 0, background: '#FFFFFF' },
}}
>
{event && <DynamicNewsDetailPanel event={event} showHeader={false} />}
</Modal>
</Drawer>
);
};

View File

@@ -2,7 +2,7 @@
import React, { useState } from 'react';
import { Card, Badge, Tag, Empty, Carousel, Tooltip } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { useDisclosure } from '@chakra-ui/react';
import { useDisclosure, useBreakpointValue } from '@chakra-ui/react';
import EventDetailModal from './EventDetailModal';
import dayjs from 'dayjs';
import './HotEvents.css';
@@ -31,6 +31,8 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
const [currentSlide, setCurrentSlide] = useState(0);
const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure();
const [modalEvent, setModalEvent] = useState(null);
// H5 端不显示 Tooltip避免触摸触发后无法消除的黑色悬浮框
const isMobile = useBreakpointValue({ base: true, md: false });
const renderPriceChange = (value) => {
if (value === null || value === undefined) {
@@ -154,21 +156,33 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
>
{/* Custom layout without Card.Meta */}
<div className="event-header">
<Tooltip title={event.title}>
{isMobile ? (
<span className="event-title">
{event.title}
</span>
</Tooltip>
) : (
<Tooltip title={event.title}>
<span className="event-title">
{event.title}
</span>
</Tooltip>
)}
<span className="event-tag">
{renderPriceChange(event.related_avg_chg)}
</span>
</div>
<Tooltip title={event.description}>
{isMobile ? (
<div className="event-description">
{event.description}
</div>
</Tooltip>
) : (
<Tooltip title={event.description}>
<div className="event-description">
{event.description}
</div>
</Tooltip>
)}
<div className="event-footer">
<span className="creator">{event.creator?.username || 'Anonymous'}</span>

View File

@@ -78,6 +78,7 @@ import {
MenuList,
MenuItem,
Collapse,
useBreakpointValue,
} from '@chakra-ui/react';
import { SearchIcon, ViewIcon, CalendarIcon, ExternalLinkIcon, StarIcon, ChevronDownIcon, InfoIcon, CloseIcon, ChevronRightIcon } from '@chakra-ui/icons';
import { FaThLarge, FaList, FaTags, FaChartLine, FaRobot, FaTable, FaHistory, FaBrain, FaLightbulb, FaRocket, FaShieldAlt, FaCalendarAlt, FaArrowUp, FaArrowDown, FaNewspaper, FaFileAlt, FaExpand, FaCompress, FaClock, FaLock } from 'react-icons/fa';
@@ -672,6 +673,10 @@ const ConceptCenter = () => {
const changePercent = concept.price_info?.avg_change_pct;
const changeColor = getChangeColor(changePercent);
const hasChange = changePercent !== null && changePercent !== undefined;
// H5 端使用更紧凑的尺寸
const isMobile = useBreakpointValue({ base: true, md: false });
const coverHeight = useBreakpointValue({ base: '100px', md: '180px' });
const logoSize = useBreakpointValue({ base: '60px', md: '120px' });
// 生成随机涨幅数字背景
const generateNumbersBackground = () => {
@@ -705,7 +710,7 @@ const ConceptCenter = () => {
boxShadow="0 4px 12px rgba(0, 0, 0, 0.1)"
>
{/* 毛玻璃涨幅数字背景 */}
<Box position="relative" height="180px" overflow="hidden">
<Box position="relative" height={coverHeight} overflow="hidden">
{/* 渐变背景层 */}
<Box
position="absolute"
@@ -757,8 +762,8 @@ const ConceptCenter = () => {
top="50%"
left="50%"
transform="translate(-50%, -50%)"
width="120px"
height="120px"
width={logoSize}
height={logoSize}
opacity={0.15}
>
<Image
@@ -849,11 +854,11 @@ const ConceptCenter = () => {
</Badge>
</Box>
<CardBody p={4}>
<VStack align="start" spacing={2}>
<CardBody p={{ base: 3, md: 4 }}>
<VStack align="start" spacing={{ base: 1, md: 2 }}>
{/* 概念名称 */}
<Heading
size="sm"
size={{ base: 'xs', md: 'sm' }}
color="gray.800"
noOfLines={1}
bgGradient="linear(to-r, purple.600, pink.600)"
@@ -863,15 +868,15 @@ const ConceptCenter = () => {
{concept.concept}
</Heading>
{/* 描述信息 */}
<Text color="gray.600" fontSize="xs" noOfLines={2} minH="32px">
{/* 描述信息 - H5端显示1行 */}
<Text color="gray.600" fontSize="xs" noOfLines={isMobile ? 1 : 2} minH={{ base: '16px', md: '32px' }}>
{concept.description || '暂无描述信息'}
</Text>
{concept.stocks && concept.stocks.length > 0 && (
<Box
width="100%"
p={3}
p={{ base: 2, md: 3 }}
bg="linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%)"
borderRadius="lg"
cursor="pointer"
@@ -886,7 +891,7 @@ const ConceptCenter = () => {
>
<Flex align="center" justify="space-between">
<Box flex={1}>
<HStack spacing={2} mb={2}>
<HStack spacing={2} mb={{ base: 1, md: 2 }}>
<Icon as={FaChartLine} boxSize={3} color="purple.500" />
<Text fontSize="xs" color="purple.700" fontWeight="bold">
热门个股
@@ -942,20 +947,20 @@ const ConceptCenter = () => {
</Box>
)}
<Divider borderColor="purple.100" />
<Divider borderColor="purple.100" my={{ base: 1, md: 0 }} />
<Flex width="100%" justify="space-between" align="center">
{formatAddedDate(concept)}
<Button
size="sm"
size={{ base: 'xs', md: 'sm' }}
leftIcon={<FaHistory />}
bgGradient="linear(to-r, purple.500, pink.500)"
color="white"
variant="solid"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)}
borderRadius="full"
px={4}
px={{ base: 2, md: 4 }}
fontWeight="medium"
boxShadow="0 4px 12px rgba(139, 92, 246, 0.3)"
_hover={{
@@ -1598,7 +1603,7 @@ const ConceptCenter = () => {
)}
{loading ? (
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>
<SimpleGrid columns={{ base: 2, md: 2, lg: 3 }} spacing={{ base: 3, md: 6 }}>
{[...Array(12)].map((_, i) => (
<SkeletonCard key={i} />
))}
@@ -1606,7 +1611,7 @@ const ConceptCenter = () => {
) : concepts.length > 0 ? (
<>
{viewMode === 'grid' ? (
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6} className="concept-grid">
<SimpleGrid columns={{ base: 2, md: 2, lg: 3 }} spacing={{ base: 3, md: 6 }} className="concept-grid">
{concepts.map((concept, index) => (
<Box key={concept.concept_id} className="concept-item" role="group">
<ConceptCard concept={concept} position={index} />