Compare commits

...

2 Commits

Author SHA1 Message Date
zdl
a7ab87f7c4 feat: StockQuoteCard 顶部导航区视觉优化
- 股票名称字号放大至 26px,字重 800,突出显示
- 添加行业标签(金融 · 银行),Badge 边框样式
- 保留指数标签(沪深300、上证180)
- Mock 数据补充 industry、industry_l1、index_tags 字段
- 类型定义新增 industry、industryL1 可选字段

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 11:30:18 +08:00
zdl
9a77bb6f0b refactor: CompanyOverview 组件 TypeScript 拆分
- 新增 index.tsx: 主组件(组合层,50 行)
- 新增 CompanyHeaderCard.tsx: 头部卡片组件(168 行)
- 新增 hooks/useCompanyOverviewData.ts: 数据加载 Hook
- 删除 index.js: 原 330 行代码精简 85%
- 修复 Company/index.js: 恢复 CompanyTabs 渲染

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 11:21:02 +08:00
9 changed files with 443 additions and 341 deletions

View File

@@ -368,6 +368,25 @@ export const stockHandlers = [
stockMap[s.code] = s.name;
});
// 行业和指数映射表
const stockIndustryMap = {
'000001': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证180'] },
'600519': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300', '上证50'] },
'300750': { industry_l1: '工业', industry: '电池', index_tags: ['创业板50'] },
'601318': { industry_l1: '金融', industry: '保险', index_tags: ['沪深300', '上证50'] },
'600036': { industry_l1: '金融', industry: '银行', index_tags: ['沪深300', '上证50'] },
'000858': { industry_l1: '消费', industry: '白酒', index_tags: ['沪深300'] },
'002594': { industry_l1: '汽车', industry: '乘用车', index_tags: ['沪深300', '创业板指'] },
};
const defaultIndustries = [
{ industry_l1: '科技', industry: '软件' },
{ industry_l1: '医药', industry: '化学制药' },
{ industry_l1: '消费', industry: '食品' },
{ industry_l1: '金融', industry: '证券' },
{ industry_l1: '工业', industry: '机械' },
];
// 为每只股票生成报价数据
const quotesData = {};
codes.forEach(stockCode => {
@@ -380,6 +399,11 @@ export const stockHandlers = [
// 昨收
const prevClose = parseFloat((basePrice - change).toFixed(2));
// 获取行业和指数信息
const codeWithoutSuffix = stockCode.replace(/\.(SH|SZ)$/i, '');
const industryInfo = stockIndustryMap[codeWithoutSuffix] ||
defaultIndustries[Math.floor(Math.random() * defaultIndustries.length)];
quotesData[stockCode] = {
code: stockCode,
name: stockMap[stockCode] || `股票${stockCode}`,
@@ -393,7 +417,11 @@ export const stockHandlers = [
volume: Math.floor(Math.random() * 100000000),
amount: parseFloat((Math.random() * 10000000000).toFixed(2)),
market: stockCode.startsWith('6') ? 'SH' : 'SZ',
update_time: new Date().toISOString()
update_time: new Date().toISOString(),
// 行业和指数标签
industry_l1: industryInfo.industry_l1,
industry: industryInfo.industry,
index_tags: industryInfo.index_tags || []
};
});

View File

@@ -0,0 +1,167 @@
// src/views/Company/components/CompanyOverview/CompanyHeaderCard.tsx
// 公司头部信息卡片组件
import React from "react";
import {
VStack,
HStack,
Text,
Badge,
Card,
CardBody,
Heading,
SimpleGrid,
Divider,
Icon,
Grid,
GridItem,
Stat,
StatLabel,
StatNumber,
Circle,
Link,
} from "@chakra-ui/react";
import {
FaBuilding,
FaMapMarkerAlt,
FaUserShield,
FaBriefcase,
FaCalendarAlt,
FaGlobe,
FaEnvelope,
FaPhone,
FaCrown,
} from "react-icons/fa";
import { ExternalLinkIcon } from "@chakra-ui/icons";
import type { CompanyHeaderCardProps } from "./types";
import { formatRegisteredCapital, formatDate } from "./utils";
/**
* 公司头部信息卡片组件
*/
const CompanyHeaderCard: React.FC<CompanyHeaderCardProps> = ({ basicInfo }) => {
return (
<Card
bg="white"
shadow="lg"
borderTop="4px solid"
borderTopColor="blue.500"
>
<CardBody>
<Grid templateColumns="repeat(12, 1fr)" gap={6}>
{/* 左侧:公司基本信息 */}
<GridItem colSpan={{ base: 12, lg: 8 }}>
<VStack align="start" spacing={4}>
{/* 公司名称和代码 */}
<HStack spacing={4}>
<Circle size="60px" bg="blue.500">
<Icon as={FaBuilding} color="white" boxSize={8} />
</Circle>
<VStack align="start" spacing={1}>
<HStack>
<Heading size="lg" color="blue.600">
{basicInfo.ORGNAME || basicInfo.SECNAME}
</Heading>
<Badge colorScheme="blue" fontSize="md" px={2} py={1}>
{basicInfo.SECCODE}
</Badge>
</HStack>
<HStack spacing={2}>
<Badge colorScheme="purple" fontSize="xs">
{basicInfo.sw_industry_l1}
</Badge>
<Badge colorScheme="orange" fontSize="xs">
{basicInfo.sw_industry_l2}
</Badge>
{basicInfo.sw_industry_l3 && (
<Badge colorScheme="green" fontSize="xs">
{basicInfo.sw_industry_l3}
</Badge>
)}
</HStack>
</VStack>
</HStack>
<Divider />
{/* 管理层信息 */}
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={3} w="full">
<HStack>
<Icon as={FaUserShield} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500"></Text>
<Text as="span" fontWeight="bold">{basicInfo.legal_representative}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaCrown} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500"></Text>
<Text as="span" fontWeight="bold">{basicInfo.chairman}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaBriefcase} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500"></Text>
<Text as="span" fontWeight="bold">{basicInfo.general_manager}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaCalendarAlt} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500"></Text>
<Text as="span" fontWeight="bold">{formatDate(basicInfo.establish_date)}</Text>
</Text>
</HStack>
</SimpleGrid>
{/* 公司简介 */}
<Text fontSize="sm" color="gray.600" noOfLines={2}>
{basicInfo.company_intro}
</Text>
</VStack>
</GridItem>
{/* 右侧:注册资本和联系方式 */}
<GridItem colSpan={{ base: 12, lg: 4 }}>
<VStack spacing={3} align="stretch">
<Stat>
<StatLabel></StatLabel>
<StatNumber fontSize="2xl" color="blue.500">
{formatRegisteredCapital(basicInfo.reg_capital)}
</StatNumber>
</Stat>
<Divider />
<VStack align="stretch" spacing={1}>
<HStack fontSize="sm">
<Icon as={FaMapMarkerAlt} color="gray.500" boxSize={3} />
<Text noOfLines={1}>{basicInfo.province} {basicInfo.city}</Text>
</HStack>
<HStack fontSize="sm">
<Icon as={FaGlobe} color="gray.500" boxSize={3} />
<Link href={basicInfo.website} isExternal color="blue.500" noOfLines={1}>
{basicInfo.website} <ExternalLinkIcon mx="2px" />
</Link>
</HStack>
<HStack fontSize="sm">
<Icon as={FaEnvelope} color="gray.500" boxSize={3} />
<Text noOfLines={1}>{basicInfo.email}</Text>
</HStack>
<HStack fontSize="sm">
<Icon as={FaPhone} color="gray.500" boxSize={3} />
<Text>{basicInfo.tel}</Text>
</HStack>
</VStack>
</VStack>
</GridItem>
</Grid>
</CardBody>
</Card>
);
};
export default CompanyHeaderCard;

View File

@@ -0,0 +1,140 @@
// src/views/Company/components/CompanyOverview/hooks/useCompanyOverviewData.ts
// 公司概览数据加载 Hook
import { useState, useEffect, useCallback } from "react";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import type {
BasicInfo,
ActualControl,
Concentration,
Management,
Shareholder,
Branch,
Announcement,
DisclosureSchedule,
CompanyOverviewData,
} from "../types";
const API_BASE_URL = getApiBase();
interface ApiResponse<T> {
success: boolean;
data: T;
}
/**
* 公司概览数据加载 Hook
* @param propStockCode - 股票代码
* @returns 公司概览数据
*/
export const useCompanyOverviewData = (propStockCode?: string): CompanyOverviewData => {
const [stockCode, setStockCode] = useState(propStockCode || "000001");
const [loading, setLoading] = useState(false);
const [dataLoaded, setDataLoaded] = useState(false);
// 基本信息数据
const [basicInfo, setBasicInfo] = useState<BasicInfo | null>(null);
const [actualControl, setActualControl] = useState<ActualControl[]>([]);
const [concentration, setConcentration] = useState<Concentration[]>([]);
const [management, setManagement] = useState<Management[]>([]);
const [topCirculationShareholders, setTopCirculationShareholders] = useState<Shareholder[]>([]);
const [topShareholders, setTopShareholders] = useState<Shareholder[]>([]);
const [branches, setBranches] = useState<Branch[]>([]);
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [disclosureSchedule, setDisclosureSchedule] = useState<DisclosureSchedule[]>([]);
// 监听 props 中的 stockCode 变化
useEffect(() => {
if (propStockCode && propStockCode !== stockCode) {
setStockCode(propStockCode);
setDataLoaded(false);
}
}, [propStockCode, stockCode]);
// 加载基本信息数据9个接口
const loadBasicInfoData = useCallback(async () => {
if (dataLoaded) return;
setLoading(true);
try {
const [
basicRes,
actualRes,
concentrationRes,
managementRes,
circulationRes,
shareholdersRes,
branchesRes,
announcementsRes,
disclosureRes,
] = await Promise.all([
fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`).then((r) =>
r.json()
) as Promise<ApiResponse<BasicInfo>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/actual-control`).then((r) =>
r.json()
) as Promise<ApiResponse<ActualControl[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/concentration`).then((r) =>
r.json()
) as Promise<ApiResponse<Concentration[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true`).then((r) =>
r.json()
) as Promise<ApiResponse<Management[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10`).then((r) =>
r.json()
) as Promise<ApiResponse<Shareholder[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`).then((r) =>
r.json()
) as Promise<ApiResponse<Branch[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20`).then((r) =>
r.json()
) as Promise<ApiResponse<Announcement[]>>,
fetch(`${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule`).then((r) =>
r.json()
) as Promise<ApiResponse<DisclosureSchedule[]>>,
]);
if (basicRes.success) setBasicInfo(basicRes.data);
if (actualRes.success) setActualControl(actualRes.data);
if (concentrationRes.success) setConcentration(concentrationRes.data);
if (managementRes.success) setManagement(managementRes.data);
if (circulationRes.success) setTopCirculationShareholders(circulationRes.data);
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
if (branchesRes.success) setBranches(branchesRes.data);
if (announcementsRes.success) setAnnouncements(announcementsRes.data);
if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data);
setDataLoaded(true);
} catch (err) {
logger.error("useCompanyOverviewData", "loadBasicInfoData", err, { stockCode });
} finally {
setLoading(false);
}
}, [stockCode, dataLoaded]);
// 首次加载
useEffect(() => {
if (stockCode) {
loadBasicInfoData();
}
}, [stockCode, loadBasicInfoData]);
return {
basicInfo,
actualControl,
concentration,
management,
topCirculationShareholders,
topShareholders,
branches,
announcements,
disclosureSchedule,
loading,
dataLoaded,
};
};

View File

@@ -1,329 +0,0 @@
// src/views/Company/components/CompanyOverview/index.js
// 公司概览 - 头部卡片 + 基本信息
import React, { useState, useEffect } from "react";
import {
Box,
VStack,
HStack,
Text,
Badge,
Card,
CardBody,
Heading,
SimpleGrid,
Divider,
Spinner,
Center,
Icon,
Grid,
GridItem,
Stat,
StatLabel,
StatNumber,
Circle,
Link,
} from "@chakra-ui/react";
import {
FaBuilding,
FaMapMarkerAlt,
FaUserShield,
FaBriefcase,
FaCalendarAlt,
FaGlobe,
FaEnvelope,
FaPhone,
FaCrown,
} from "react-icons/fa";
import { ExternalLinkIcon } from "@chakra-ui/icons";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
// 子组件
import BasicInfoTab from "./BasicInfoTab";
// API配置
const API_BASE_URL = getApiBase();
// 格式化工具
const formatUtils = {
formatRegisteredCapital: (value) => {
if (!value && value !== 0) return "-";
const absValue = Math.abs(value);
if (absValue >= 100000) {
return (value / 10000).toFixed(2) + "亿元";
}
return value.toFixed(2) + "万元";
},
formatDate: (dateString) => {
if (!dateString) return "-";
return new Date(dateString).toLocaleDateString("zh-CN");
},
};
/**
* 公司概览组件
*
* 功能:
* - 显示公司头部信息卡片
* - 显示基本信息(股权结构、管理层、公告等)
*
* @param {Object} props
* @param {string} props.stockCode - 股票代码
*/
const CompanyOverview = ({ stockCode: propStockCode }) => {
const [stockCode, setStockCode] = useState(propStockCode || "000001");
const [loading, setLoading] = useState(false);
const [dataLoaded, setDataLoaded] = useState(false);
// 监听 props 中的 stockCode 变化
useEffect(() => {
if (propStockCode && propStockCode !== stockCode) {
setStockCode(propStockCode);
setDataLoaded(false);
}
}, [propStockCode, stockCode]);
// 基本信息数据
const [basicInfo, setBasicInfo] = useState(null);
const [actualControl, setActualControl] = useState([]);
const [concentration, setConcentration] = useState([]);
const [management, setManagement] = useState([]);
const [topCirculationShareholders, setTopCirculationShareholders] = useState([]);
const [topShareholders, setTopShareholders] = useState([]);
const [branches, setBranches] = useState([]);
const [announcements, setAnnouncements] = useState([]);
const [disclosureSchedule, setDisclosureSchedule] = useState([]);
const [_error, setError] = useState(null);
// 加载基本信息数据9个接口
const loadBasicInfoData = async () => {
if (dataLoaded) return;
setLoading(true);
setError(null);
try {
const requests = [
fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`).then((r) =>
r.json()
),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/actual-control`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/concentration`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10`
).then((r) => r.json()),
fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`).then((r) =>
r.json()
),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule`
).then((r) => r.json()),
];
const [
basicRes,
actualRes,
concentrationRes,
managementRes,
circulationRes,
shareholdersRes,
branchesRes,
announcementsRes,
disclosureRes,
] = await Promise.all(requests);
if (basicRes.success) setBasicInfo(basicRes.data);
if (actualRes.success) setActualControl(actualRes.data);
if (concentrationRes.success) setConcentration(concentrationRes.data);
if (managementRes.success) setManagement(managementRes.data);
if (circulationRes.success)
setTopCirculationShareholders(circulationRes.data);
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
if (branchesRes.success) setBranches(branchesRes.data);
if (announcementsRes.success) setAnnouncements(announcementsRes.data);
if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data);
setDataLoaded(true);
} catch (err) {
setError(err.message);
logger.error("CompanyOverview", "loadBasicInfoData", err, { stockCode });
} finally {
setLoading(false);
}
};
// 首次加载
useEffect(() => {
if (stockCode) {
loadBasicInfoData();
}
}, [stockCode]);
if (loading && !basicInfo) {
return (
<Center h="300px">
<VStack spacing={4}>
<Spinner size="xl" color="blue.500" thickness="4px" />
<Text>正在加载公司概览数据...</Text>
</VStack>
</Center>
);
}
return (
<VStack spacing={6} align="stretch">
{/* 公司头部信息卡片 */}
{basicInfo && (
<Card
bg="white"
shadow="lg"
borderTop="4px solid"
borderTopColor="blue.500"
>
<CardBody>
<Grid templateColumns="repeat(12, 1fr)" gap={6}>
<GridItem colSpan={{ base: 12, lg: 8 }}>
<VStack align="start" spacing={4}>
<HStack spacing={4}>
<Circle size="60px" bg="blue.500">
<Icon as={FaBuilding} color="white" boxSize={8} />
</Circle>
<VStack align="start" spacing={1}>
<HStack>
<Heading size="lg" color="blue.600">
{basicInfo.ORGNAME || basicInfo.SECNAME}
</Heading>
<Badge colorScheme="blue" fontSize="md" px={2} py={1}>
{basicInfo.SECCODE}
</Badge>
</HStack>
<HStack spacing={2}>
<Badge colorScheme="purple" fontSize="xs">
{basicInfo.sw_industry_l1}
</Badge>
<Badge colorScheme="orange" fontSize="xs">
{basicInfo.sw_industry_l2}
</Badge>
{basicInfo.sw_industry_l3 && (
<Badge colorScheme="green" fontSize="xs">
{basicInfo.sw_industry_l3}
</Badge>
)}
</HStack>
</VStack>
</HStack>
<Divider />
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={3} w="full">
<HStack>
<Icon as={FaUserShield} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500">法定代表人</Text>
<Text as="span" fontWeight="bold">{basicInfo.legal_representative}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaCrown} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500">董事长</Text>
<Text as="span" fontWeight="bold">{basicInfo.chairman}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaBriefcase} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500">总经理</Text>
<Text as="span" fontWeight="bold">{basicInfo.general_manager}</Text>
</Text>
</HStack>
<HStack>
<Icon as={FaCalendarAlt} color="gray.500" boxSize={4} />
<Text fontSize="sm">
<Text as="span" color="gray.500">成立日期</Text>
<Text as="span" fontWeight="bold">{formatUtils.formatDate(basicInfo.establish_date)}</Text>
</Text>
</HStack>
</SimpleGrid>
<Text fontSize="sm" color="gray.600" noOfLines={2}>
{basicInfo.company_intro}
</Text>
</VStack>
</GridItem>
<GridItem colSpan={{ base: 12, lg: 4 }}>
<VStack spacing={3} align="stretch">
<Stat>
<StatLabel>注册资本</StatLabel>
<StatNumber fontSize="2xl" color="blue.500">
{formatUtils.formatRegisteredCapital(basicInfo.reg_capital)}
</StatNumber>
</Stat>
<Divider />
<VStack align="stretch" spacing={1}>
<HStack fontSize="sm">
<Icon as={FaMapMarkerAlt} color="gray.500" boxSize={3} />
<Text noOfLines={1}>{basicInfo.province} {basicInfo.city}</Text>
</HStack>
<HStack fontSize="sm">
<Icon as={FaGlobe} color="gray.500" boxSize={3} />
<Link href={basicInfo.website} isExternal color="blue.500" noOfLines={1}>
{basicInfo.website} <ExternalLinkIcon mx="2px" />
</Link>
</HStack>
<HStack fontSize="sm">
<Icon as={FaEnvelope} color="gray.500" boxSize={3} />
<Text noOfLines={1}>{basicInfo.email}</Text>
</HStack>
<HStack fontSize="sm">
<Icon as={FaPhone} color="gray.500" boxSize={3} />
<Text>{basicInfo.tel}</Text>
</HStack>
</VStack>
</VStack>
</GridItem>
</Grid>
</CardBody>
</Card>
)}
{/* 基本信息内容 */}
<BasicInfoTab
basicInfo={basicInfo}
actualControl={actualControl}
concentration={concentration}
topShareholders={topShareholders}
topCirculationShareholders={topCirculationShareholders}
management={management}
announcements={announcements}
branches={branches}
disclosureSchedule={disclosureSchedule}
cardBg="white"
loading={loading}
/>
</VStack>
);
};
export default CompanyOverview;

View File

@@ -0,0 +1,70 @@
// src/views/Company/components/CompanyOverview/index.tsx
// 公司概览 - 主组件(组合层)
import React from "react";
import { VStack, Spinner, Center, Text } from "@chakra-ui/react";
import { useCompanyOverviewData } from "./hooks/useCompanyOverviewData";
import CompanyHeaderCard from "./CompanyHeaderCard";
import type { CompanyOverviewProps } from "./types";
// 子组件(暂保持 JS
import BasicInfoTab from "./BasicInfoTab";
/**
* 公司概览组件
*
* 功能:
* - 显示公司头部信息卡片
* - 显示基本信息(股权结构、管理层、公告等)
*/
const CompanyOverview: React.FC<CompanyOverviewProps> = ({ stockCode }) => {
const {
basicInfo,
actualControl,
concentration,
management,
topCirculationShareholders,
topShareholders,
branches,
announcements,
disclosureSchedule,
loading,
} = useCompanyOverviewData(stockCode);
// 加载状态
if (loading && !basicInfo) {
return (
<Center h="300px">
<VStack spacing={4}>
<Spinner size="xl" color="blue.500" thickness="4px" />
<Text>...</Text>
</VStack>
</Center>
);
}
return (
<VStack spacing={6} align="stretch">
{/* 公司头部信息卡片 */}
{basicInfo && <CompanyHeaderCard basicInfo={basicInfo} />}
{/* 基本信息内容 */}
<BasicInfoTab
basicInfo={basicInfo}
actualControl={actualControl}
concentration={concentration}
topShareholders={topShareholders}
topCirculationShareholders={topCirculationShareholders}
management={management}
announcements={announcements}
branches={branches}
disclosureSchedule={disclosureSchedule}
cardBg="white"
loading={loading}
/>
</VStack>
);
};
export default CompanyOverview;

View File

@@ -97,18 +97,40 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
<CardBody>
{/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
<Flex justify="space-between" align="center" mb={4}>
{/* 左侧:名称(代码) | 指数标签 */}
<HStack spacing={2}>
<Text fontSize="22px" fontWeight="bold" color={valueColor}>
{data.name}{data.code}
{/* 左侧:股票名称 + 行业标签 + 指数标签 */}
<HStack spacing={3} align="center">
{/* 股票名称 - 突出显示 */}
<Text fontSize="26px" fontWeight="800" color={valueColor}>
{data.name}
</Text>
<Text fontSize="18px" fontWeight="normal" color={labelColor}>
({data.code})
</Text>
{/* 行业标签 */}
{(data.industryL1 || data.industry) && (
<Badge
bg="transparent"
color={labelColor}
fontSize="14px"
fontWeight="medium"
border="1px solid"
borderColor={borderColor}
px={2}
py={0.5}
borderRadius="md"
>
{data.industryL1 && data.industry
? `${data.industryL1} · ${data.industry}`
: data.industry || data.industryL1}
</Badge>
)}
{/* 指数标签 */}
{data.indexTags?.length > 0 && (
<>
<Text color={labelColor} fontSize="22px" fontWeight="light">|</Text>
<Text fontSize="16px" color={labelColor}>
{data.indexTags.join('、')}
</Text>
</>
<Text fontSize="14px" color={labelColor}>
{data.indexTags.join('、')}
</Text>
)}
</HStack>

View File

@@ -10,6 +10,8 @@ export interface StockQuoteCardData {
name: string; // 股票名称
code: string; // 股票代码
indexTags: string[]; // 指数标签(如 沪深300、上证50
industry?: string; // 所属行业(二级),如 "银行"
industryL1?: string; // 一级行业,如 "金融"
// 价格信息
currentPrice: number; // 当前价格

View File

@@ -16,6 +16,8 @@ const transformQuoteData = (apiData, stockCode) => {
name: apiData.name || apiData.stock_name || '未知',
code: apiData.code || apiData.stock_code || stockCode,
indexTags: apiData.index_tags || apiData.indexTags || [],
industry: apiData.industry || apiData.sw_industry_l2 || '',
industryL1: apiData.industry_l1 || apiData.sw_industry_l1 || '',
// 价格信息
currentPrice: apiData.current_price || apiData.currentPrice || apiData.close || 0,

View File

@@ -98,7 +98,7 @@ const CompanyIndex = () => {
/>
{/* Tab 切换区域:概览、行情、财务、预测 */}
{/* <CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/> */}
<CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/>
</VStack>
</Container>
);