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 切换区域:概览、行情、财务、预测 */}
|
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
||||||
{/* <CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/> */}
|
<CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user