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>
This commit is contained in:
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
70
src/views/Company/components/CompanyOverview/index.tsx
Normal file
70
src/views/Company/components/CompanyOverview/index.tsx
Normal 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;
|
||||
@@ -98,7 +98,7 @@ const CompanyIndex = () => {
|
||||
/>
|
||||
|
||||
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
||||
{/* <CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/> */}
|
||||
<CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/>
|
||||
</VStack>
|
||||
</Container>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user