更新Company页面的UI为FUI风格
This commit is contained in:
@@ -56,16 +56,16 @@ export interface SubTabTheme {
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设主题
|
||||
* 预设主题 - FUI 风格优化
|
||||
*/
|
||||
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
||||
blackGold: {
|
||||
bg: 'gray.900',
|
||||
borderColor: 'rgba(212, 175, 55, 0.3)',
|
||||
tabSelectedBg: '#D4AF37',
|
||||
tabSelectedColor: 'gray.900',
|
||||
tabUnselectedColor: '#D4AF37',
|
||||
tabHoverBg: 'gray.600',
|
||||
bg: 'transparent',
|
||||
borderColor: 'rgba(212, 175, 55, 0.2)',
|
||||
tabSelectedBg: 'linear-gradient(135deg, #D4AF37 0%, #B8960C 100%)',
|
||||
tabSelectedColor: '#0A0A14',
|
||||
tabUnselectedColor: 'rgba(212, 175, 55, 0.8)',
|
||||
tabHoverBg: 'rgba(212, 175, 55, 0.1)',
|
||||
},
|
||||
default: {
|
||||
bg: 'white',
|
||||
@@ -179,23 +179,28 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
||||
key={tab.key}
|
||||
color={theme.tabUnselectedColor}
|
||||
borderRadius="full"
|
||||
px={2.5}
|
||||
px={3}
|
||||
py={1.5}
|
||||
fontSize="xs"
|
||||
whiteSpace="nowrap"
|
||||
flexShrink={0}
|
||||
border="1px solid transparent"
|
||||
transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
|
||||
_selected={{
|
||||
bg: theme.tabSelectedBg,
|
||||
color: theme.tabSelectedColor,
|
||||
fontWeight: 'bold',
|
||||
boxShadow: '0 0 12px rgba(212, 175, 55, 0.4)',
|
||||
border: '1px solid rgba(212, 175, 55, 0.5)',
|
||||
}}
|
||||
_hover={{
|
||||
bg: theme.tabHoverBg,
|
||||
border: '1px solid rgba(212, 175, 55, 0.3)',
|
||||
}}
|
||||
>
|
||||
<HStack spacing={1}>
|
||||
{tab.icon && <Icon as={tab.icon} boxSize={3} />}
|
||||
<Text>{tab.name}</Text>
|
||||
<HStack spacing={1.5}>
|
||||
{tab.icon && <Icon as={tab.icon} boxSize={3.5} />}
|
||||
<Text letterSpacing="wide">{tab.name}</Text>
|
||||
</HStack>
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* Company 页面顶部搜索栏组件
|
||||
* - 显示股票代码、名称、价格、涨跌幅
|
||||
* - 股票搜索功能
|
||||
* - 自选股操作
|
||||
* Company 页面顶部搜索栏组件 - FUI 科幻风格
|
||||
*
|
||||
* 设计特点:
|
||||
* - Glassmorphism 毛玻璃背景
|
||||
* - 发光效果和微动画
|
||||
* - Ash Thorp 风格的数据展示
|
||||
* - James Turrell 柔和光影
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo, useCallback, useState } from 'react';
|
||||
@@ -14,17 +17,24 @@ import {
|
||||
Text,
|
||||
Button,
|
||||
Icon,
|
||||
Badge,
|
||||
Skeleton,
|
||||
keyframes,
|
||||
} from '@chakra-ui/react';
|
||||
import { AutoComplete, Spin } from 'antd';
|
||||
import { Search, Star } from 'lucide-react';
|
||||
import { Search, Star, TrendingUp, TrendingDown } from 'lucide-react';
|
||||
import { useStockSearch } from '@hooks/useStockSearch';
|
||||
import { THEME, getSearchBoxStyles } from '../../config';
|
||||
import { FUI_COLORS, FUI_GLOW, FUI_ANIMATION, FUI_GLASS } from '../../theme/fui';
|
||||
import type { CompanyHeaderProps, StockSearchResult } from '../../types';
|
||||
|
||||
// 发光脉冲动画
|
||||
const glowPulse = keyframes`
|
||||
0%, 100% { box-shadow: 0 0 20px rgba(212, 175, 55, 0.2); }
|
||||
50% { box-shadow: 0 0 30px rgba(212, 175, 55, 0.4); }
|
||||
`;
|
||||
|
||||
/**
|
||||
* 股票信息展示组件
|
||||
* 股票信息展示组件 - FUI 风格
|
||||
*/
|
||||
const StockInfoDisplay = memo<{
|
||||
stockCode: string;
|
||||
@@ -35,47 +45,95 @@ const StockInfoDisplay = memo<{
|
||||
}>(({ stockCode, stockName, price, change, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<VStack align="start" spacing={1}>
|
||||
<Skeleton height="32px" width="200px" />
|
||||
<Skeleton height="24px" width="150px" />
|
||||
<VStack align="start" spacing={2}>
|
||||
<Skeleton height="36px" width="220px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||
<Skeleton height="28px" width="160px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
const isPositive = change !== null && change !== undefined && change >= 0;
|
||||
const TrendIcon = isPositive ? TrendingUp : TrendingDown;
|
||||
|
||||
return (
|
||||
<VStack align="start" spacing={0}>
|
||||
<HStack spacing={2}>
|
||||
<VStack align="start" spacing={1}>
|
||||
{/* 股票代码 & 名称 */}
|
||||
<HStack spacing={3} align="baseline">
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
color={THEME.gold}
|
||||
color={FUI_COLORS.gold[400]}
|
||||
letterSpacing="wider"
|
||||
textShadow={FUI_GLOW.text.gold}
|
||||
>
|
||||
{stockCode}
|
||||
</Text>
|
||||
{stockName && (
|
||||
<Text fontSize="xl" fontWeight="medium" color={THEME.textPrimary}>
|
||||
<Text
|
||||
fontSize="xl"
|
||||
fontWeight="medium"
|
||||
color={FUI_COLORS.text.primary}
|
||||
letterSpacing="wide"
|
||||
>
|
||||
{stockName}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 价格 & 涨跌幅 */}
|
||||
{price !== null && price !== undefined && (
|
||||
<HStack spacing={3} mt={1}>
|
||||
<Text fontSize="lg" fontWeight="bold" color={THEME.textPrimary}>
|
||||
<HStack spacing={4} mt={1}>
|
||||
{/* 价格 */}
|
||||
<HStack spacing={1}>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={FUI_COLORS.text.muted}
|
||||
textTransform="uppercase"
|
||||
letterSpacing="wider"
|
||||
>
|
||||
Price
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="xl"
|
||||
fontWeight="bold"
|
||||
color={FUI_COLORS.text.primary}
|
||||
fontFamily="mono"
|
||||
>
|
||||
¥{price.toFixed(2)}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
{/* 涨跌幅 Badge */}
|
||||
{change !== null && change !== undefined && (
|
||||
<Badge
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
bg={change >= 0 ? THEME.positiveBg : THEME.negativeBg}
|
||||
color={change >= 0 ? THEME.positive : THEME.negative}
|
||||
<Box
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
bg={isPositive ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'}
|
||||
border="1px solid"
|
||||
borderColor={isPositive ? 'rgba(239, 68, 68, 0.3)' : 'rgba(34, 197, 94, 0.3)'}
|
||||
boxShadow={isPositive
|
||||
? '0 0 12px rgba(239, 68, 68, 0.2)'
|
||||
: '0 0 12px rgba(34, 197, 94, 0.2)'
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
as={TrendIcon}
|
||||
boxSize={3.5}
|
||||
color={isPositive ? FUI_COLORS.status.positive : FUI_COLORS.status.negative}
|
||||
/>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
fontFamily="mono"
|
||||
color={isPositive ? FUI_COLORS.status.positive : FUI_COLORS.status.negative}
|
||||
>
|
||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
||||
</Badge>
|
||||
{isPositive ? '+' : ''}{change.toFixed(2)}%
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
@@ -154,8 +212,36 @@ const SearchActions = memo<{
|
||||
|
||||
return (
|
||||
<HStack spacing={3}>
|
||||
{/* 搜索框 */}
|
||||
<Box sx={getSearchBoxStyles(THEME)}>
|
||||
{/* 搜索框 - FUI 风格 */}
|
||||
<Box
|
||||
sx={{
|
||||
...getSearchBoxStyles(THEME),
|
||||
'.ant-select-selector': {
|
||||
backgroundColor: `${FUI_COLORS.bg.primary} !important`,
|
||||
borderColor: `${FUI_COLORS.line.default} !important`,
|
||||
borderRadius: '10px !important',
|
||||
height: '42px !important',
|
||||
backdropFilter: FUI_GLASS.blur.sm,
|
||||
transition: `all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`,
|
||||
'&:hover': {
|
||||
borderColor: `${FUI_COLORS.line.emphasis} !important`,
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
},
|
||||
'&:focus-within': {
|
||||
borderColor: `${FUI_COLORS.gold[400]} !important`,
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
},
|
||||
},
|
||||
'.ant-select-selection-search-input': {
|
||||
color: `${FUI_COLORS.text.primary} !important`,
|
||||
height: '40px !important',
|
||||
fontFamily: 'inherit',
|
||||
},
|
||||
'.ant-select-selection-placeholder': {
|
||||
color: `${FUI_COLORS.text.muted} !important`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AutoComplete
|
||||
value={inputCode}
|
||||
options={stockOptions}
|
||||
@@ -163,36 +249,57 @@ const SearchActions = memo<{
|
||||
onSelect={handleSelect}
|
||||
onChange={onInputChange}
|
||||
placeholder="输入代码、名称或拼音"
|
||||
style={{ width: 220 }}
|
||||
style={{ width: 240 }}
|
||||
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 搜索按钮 */}
|
||||
{/* 搜索按钮 - 发光效果 */}
|
||||
<Button
|
||||
bg={THEME.gold}
|
||||
color={THEME.bg}
|
||||
_hover={{ bg: THEME.goldLight }}
|
||||
_active={{ bg: THEME.goldDark }}
|
||||
bg={`linear-gradient(135deg, ${FUI_COLORS.gold[500]} 0%, ${FUI_COLORS.gold[400]} 100%)`}
|
||||
color={FUI_COLORS.bg.deep}
|
||||
_hover={{
|
||||
bg: `linear-gradient(135deg, ${FUI_COLORS.gold[400]} 0%, ${FUI_COLORS.gold[300]} 100%)`,
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
transform: 'translateY(-1px)',
|
||||
}}
|
||||
_active={{
|
||||
bg: FUI_COLORS.gold[600],
|
||||
transform: 'translateY(0)',
|
||||
}}
|
||||
size="md"
|
||||
h="42px"
|
||||
px={5}
|
||||
onClick={onSearch}
|
||||
leftIcon={<Icon as={Search} boxSize={4} />}
|
||||
fontWeight="bold"
|
||||
borderRadius="10px"
|
||||
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
|
||||
{/* 自选按钮 */}
|
||||
{/* 自选按钮 - FUI 风格 */}
|
||||
<Button
|
||||
variant={isInWatchlist ? 'solid' : 'outline'}
|
||||
bg={isInWatchlist ? THEME.gold : 'transparent'}
|
||||
color={isInWatchlist ? THEME.bg : THEME.gold}
|
||||
borderColor={THEME.gold}
|
||||
variant="outline"
|
||||
bg={isInWatchlist ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={FUI_COLORS.gold[400]}
|
||||
borderColor={isInWatchlist ? FUI_COLORS.gold[400] : FUI_COLORS.line.emphasis}
|
||||
borderWidth="1px"
|
||||
_hover={{
|
||||
bg: isInWatchlist ? THEME.goldLight : 'rgba(212, 175, 55, 0.1)',
|
||||
bg: 'rgba(212, 175, 55, 0.15)',
|
||||
borderColor: FUI_COLORS.gold[400],
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
transform: 'translateY(-1px)',
|
||||
}}
|
||||
_active={{
|
||||
bg: 'rgba(212, 175, 55, 0.25)',
|
||||
transform: 'translateY(0)',
|
||||
}}
|
||||
size="md"
|
||||
h="42px"
|
||||
px={5}
|
||||
onClick={onWatchlistToggle}
|
||||
isLoading={watchlistLoading}
|
||||
leftIcon={
|
||||
@@ -200,9 +307,13 @@ const SearchActions = memo<{
|
||||
as={Star}
|
||||
boxSize={4}
|
||||
fill={isInWatchlist ? 'currentColor' : 'none'}
|
||||
filter={isInWatchlist ? 'drop-shadow(0 0 4px rgba(212, 175, 55, 0.6))' : 'none'}
|
||||
/>
|
||||
}
|
||||
fontWeight="bold"
|
||||
borderRadius="10px"
|
||||
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||
sx={isInWatchlist ? { animation: `${glowPulse} 3s ease-in-out infinite` } : undefined}
|
||||
>
|
||||
{isInWatchlist ? '已自选' : '自选'}
|
||||
</Button>
|
||||
@@ -248,13 +359,42 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
|
||||
|
||||
return (
|
||||
<Box
|
||||
bg={THEME.cardBg}
|
||||
position="relative"
|
||||
bg={`linear-gradient(180deg, ${FUI_COLORS.bg.elevated} 0%, ${FUI_COLORS.bg.primary} 100%)`}
|
||||
borderBottom="1px solid"
|
||||
borderColor={THEME.border}
|
||||
borderColor={FUI_COLORS.line.default}
|
||||
px={6}
|
||||
py={4}
|
||||
py={5}
|
||||
backdropFilter={FUI_GLASS.blur.md}
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* 环境光效果 - James Turrell 风格 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
pointerEvents="none"
|
||||
bg={`radial-gradient(ellipse 80% 50% at 20% 40%, ${FUI_COLORS.ambient.warm}, transparent),
|
||||
radial-gradient(ellipse 60% 40% at 80% 60%, ${FUI_COLORS.ambient.cool}, transparent)`}
|
||||
opacity={0.6}
|
||||
/>
|
||||
|
||||
{/* 顶部发光线 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left="10%"
|
||||
right="10%"
|
||||
h="1px"
|
||||
bg={`linear-gradient(90deg, transparent 0%, ${FUI_COLORS.gold[400]} 50%, transparent 100%)`}
|
||||
opacity={0.4}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
position="relative"
|
||||
zIndex={1}
|
||||
maxW="container.xl"
|
||||
mx="auto"
|
||||
justify="space-between"
|
||||
|
||||
@@ -382,22 +382,6 @@ export const getMinuteKLineOption = (theme: Theme, minuteData: MinuteData | null
|
||||
end: 100,
|
||||
minValueSpan: 20,
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
xAxisIndex: [0, 1],
|
||||
type: 'slider',
|
||||
top: '95%',
|
||||
start: 70,
|
||||
end: 100,
|
||||
height: 20,
|
||||
handleSize: '100%',
|
||||
handleStyle: {
|
||||
color: theme.primary,
|
||||
},
|
||||
textStyle: {
|
||||
color: theme.textMuted,
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
@@ -811,24 +795,6 @@ export const getMinuteKLineDarkGoldOption = (minuteData: MinuteData | null): ECh
|
||||
end: 100,
|
||||
minValueSpan: 20,
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
xAxisIndex: [0, 1],
|
||||
type: 'slider',
|
||||
top: '95%',
|
||||
start: 70,
|
||||
end: 100,
|
||||
height: 20,
|
||||
handleSize: '100%',
|
||||
handleStyle: {
|
||||
color: gold,
|
||||
},
|
||||
textStyle: {
|
||||
color: textMuted,
|
||||
},
|
||||
borderColor: borderColor,
|
||||
fillerColor: 'rgba(212, 175, 55, 0.2)',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
/**
|
||||
* 公司详情页面
|
||||
* 公司详情页面 - FUI 科幻风格
|
||||
*
|
||||
* 特性:
|
||||
* - 黑金主题设计
|
||||
* - 懒加载 Tab 内容
|
||||
* - memo 性能优化
|
||||
* - axios 数据请求
|
||||
* - Ash Thorp 风格 FUI 设计
|
||||
* - James Turrell 光影效果
|
||||
* - Glassmorphism 毛玻璃卡片
|
||||
* - Linear.app 风格微交互
|
||||
* - HeroUI 现代组件风格
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback, useRef, useEffect, Suspense } from 'react';
|
||||
|
||||
// FUI 动画样式
|
||||
import './theme/fui-animations.css';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
||||
import SubTabContainer from '@components/SubTabContainer';
|
||||
@@ -30,7 +34,7 @@ const TabLoadingFallback = memo(() => (
|
||||
TabLoadingFallback.displayName = 'TabLoadingFallback';
|
||||
|
||||
// ============================================
|
||||
// 主内容区组件
|
||||
// 主内容区组件 - FUI 风格
|
||||
// ============================================
|
||||
|
||||
interface CompanyContentProps {
|
||||
@@ -41,12 +45,61 @@ interface CompanyContentProps {
|
||||
const CompanyContent = memo<CompanyContentProps>(({ stockCode, onTabChange }) => (
|
||||
<Box maxW="container.xl" mx="auto" px={4} py={6}>
|
||||
<Box
|
||||
bg={THEME.cardBg}
|
||||
position="relative"
|
||||
bg={`linear-gradient(145deg, rgba(26, 26, 46, 0.95) 0%, rgba(15, 15, 26, 0.98) 100%)`}
|
||||
borderRadius="xl"
|
||||
border="1px solid"
|
||||
borderColor={THEME.border}
|
||||
borderColor="rgba(212, 175, 55, 0.15)"
|
||||
overflow="hidden"
|
||||
backdropFilter="blur(16px)"
|
||||
boxShadow="0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05)"
|
||||
>
|
||||
{/* 角落装饰 - FUI 风格 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top="12px"
|
||||
left="12px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
borderTop="2px solid"
|
||||
borderLeft="2px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.4)"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<Box
|
||||
position="absolute"
|
||||
top="12px"
|
||||
right="12px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
borderTop="2px solid"
|
||||
borderRight="2px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.4)"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="12px"
|
||||
left="12px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
borderBottom="2px solid"
|
||||
borderLeft="2px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.4)"
|
||||
opacity={0.6}
|
||||
/>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="12px"
|
||||
right="12px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
borderBottom="2px solid"
|
||||
borderRight="2px solid"
|
||||
borderColor="rgba(212, 175, 55, 0.4)"
|
||||
opacity={0.6}
|
||||
/>
|
||||
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SubTabContainer
|
||||
tabs={TAB_CONFIG}
|
||||
@@ -128,8 +181,30 @@ const CompanyIndex: React.FC = () => {
|
||||
}, [trackTabChanged]);
|
||||
|
||||
return (
|
||||
<Box bg={THEME.bg} minH="calc(100vh - 60px)">
|
||||
<Box
|
||||
position="relative"
|
||||
bg={THEME.bg}
|
||||
minH="calc(100vh - 60px)"
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* 全局环境光效果 - James Turrell 风格 */}
|
||||
<Box
|
||||
position="fixed"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
pointerEvents="none"
|
||||
zIndex={0}
|
||||
bg={`
|
||||
radial-gradient(ellipse 100% 80% at 50% -20%, rgba(212, 175, 55, 0.08), transparent 50%),
|
||||
radial-gradient(ellipse 60% 50% at 0% 50%, rgba(100, 200, 255, 0.04), transparent 40%),
|
||||
radial-gradient(ellipse 60% 50% at 100% 50%, rgba(255, 200, 100, 0.04), transparent 40%)
|
||||
`}
|
||||
/>
|
||||
|
||||
{/* 顶部搜索栏 */}
|
||||
<Box position="relative" zIndex={1}>
|
||||
<CompanyHeader
|
||||
stockCode={stockCode}
|
||||
stockInfo={stockInfo}
|
||||
@@ -139,13 +214,16 @@ const CompanyIndex: React.FC = () => {
|
||||
onStockChange={handleStockChange}
|
||||
onWatchlistToggle={handleWatchlistToggle}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<Box position="relative" zIndex={1}>
|
||||
<CompanyContent
|
||||
stockCode={stockCode}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
381
src/views/Company/theme/components/FUIElements.tsx
Normal file
381
src/views/Company/theme/components/FUIElements.tsx
Normal file
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* FUI Elements - 科幻风格 UI 元素集合
|
||||
*
|
||||
* 包含:
|
||||
* - GlowText: 发光文字
|
||||
* - DataBadge: 数据标签
|
||||
* - StatusIndicator: 状态指示器
|
||||
* - Divider: 分隔线
|
||||
* - ProgressBar: 进度条
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Box, Text, HStack, BoxProps, TextProps } from '@chakra-ui/react';
|
||||
import { FUI_COLORS, FUI_GLOW, FUI_ANIMATION } from '../fui';
|
||||
|
||||
// ============================================
|
||||
// GlowText - 发光文字
|
||||
// ============================================
|
||||
|
||||
export interface GlowTextProps extends Omit<TextProps, 'css'> {
|
||||
children: React.ReactNode;
|
||||
/** 发光颜色 */
|
||||
glowColor?: 'gold' | 'white' | 'positive' | 'negative';
|
||||
/** 发光强度 */
|
||||
intensity?: 'low' | 'medium' | 'high';
|
||||
}
|
||||
|
||||
const GLOW_COLORS = {
|
||||
gold: FUI_COLORS.gold[400],
|
||||
white: '#FFFFFF',
|
||||
positive: FUI_COLORS.status.positive,
|
||||
negative: FUI_COLORS.status.negative,
|
||||
};
|
||||
|
||||
const INTENSITY_MAP = {
|
||||
low: 0.3,
|
||||
medium: 0.5,
|
||||
high: 0.8,
|
||||
};
|
||||
|
||||
export const GlowText: React.FC<GlowTextProps> = memo(({
|
||||
children,
|
||||
glowColor = 'gold',
|
||||
intensity = 'medium',
|
||||
...props
|
||||
}) => {
|
||||
const color = GLOW_COLORS[glowColor];
|
||||
const alpha = INTENSITY_MAP[intensity];
|
||||
|
||||
return (
|
||||
<Text
|
||||
color={color}
|
||||
textShadow={`0 0 ${10 * alpha}px rgba(${glowColor === 'gold' ? '212, 175, 55' : '255, 255, 255'}, ${alpha})`}
|
||||
transition={`all ${FUI_ANIMATION.duration.normal} ${FUI_ANIMATION.easing.smooth}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
GlowText.displayName = 'GlowText';
|
||||
|
||||
// ============================================
|
||||
// DataBadge - 数据标签
|
||||
// ============================================
|
||||
|
||||
export interface DataBadgeProps extends Omit<BoxProps, 'css'> {
|
||||
children: React.ReactNode;
|
||||
/** 标签类型 */
|
||||
variant?: 'default' | 'success' | 'danger' | 'warning' | 'info';
|
||||
/** 尺寸 */
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
/** 是否脉冲动画 */
|
||||
pulse?: boolean;
|
||||
}
|
||||
|
||||
const BADGE_VARIANTS = {
|
||||
default: {
|
||||
bg: 'rgba(212, 175, 55, 0.15)',
|
||||
borderColor: 'rgba(212, 175, 55, 0.3)',
|
||||
color: FUI_COLORS.gold[400],
|
||||
},
|
||||
success: {
|
||||
bg: 'rgba(34, 197, 94, 0.15)',
|
||||
borderColor: 'rgba(34, 197, 94, 0.3)',
|
||||
color: FUI_COLORS.status.negative,
|
||||
},
|
||||
danger: {
|
||||
bg: 'rgba(239, 68, 68, 0.15)',
|
||||
borderColor: 'rgba(239, 68, 68, 0.3)',
|
||||
color: FUI_COLORS.status.positive,
|
||||
},
|
||||
warning: {
|
||||
bg: 'rgba(245, 158, 11, 0.15)',
|
||||
borderColor: 'rgba(245, 158, 11, 0.3)',
|
||||
color: FUI_COLORS.status.warning,
|
||||
},
|
||||
info: {
|
||||
bg: 'rgba(59, 130, 246, 0.15)',
|
||||
borderColor: 'rgba(59, 130, 246, 0.3)',
|
||||
color: FUI_COLORS.status.info,
|
||||
},
|
||||
};
|
||||
|
||||
const BADGE_SIZES = {
|
||||
sm: { px: 2, py: 0.5, fontSize: '10px' },
|
||||
md: { px: 3, py: 1, fontSize: '12px' },
|
||||
lg: { px: 4, py: 1.5, fontSize: '14px' },
|
||||
};
|
||||
|
||||
export const DataBadge: React.FC<DataBadgeProps> = memo(({
|
||||
children,
|
||||
variant = 'default',
|
||||
size = 'md',
|
||||
pulse = false,
|
||||
...props
|
||||
}) => {
|
||||
const variantStyle = BADGE_VARIANTS[variant];
|
||||
const sizeStyle = BADGE_SIZES[size];
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
bg={variantStyle.bg}
|
||||
border="1px solid"
|
||||
borderColor={variantStyle.borderColor}
|
||||
borderRadius="full"
|
||||
color={variantStyle.color}
|
||||
fontWeight="medium"
|
||||
letterSpacing="0.5px"
|
||||
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||
sx={pulse ? { animation: 'glowPulse 2s ease-in-out infinite' } : undefined}
|
||||
{...sizeStyle}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
DataBadge.displayName = 'DataBadge';
|
||||
|
||||
// ============================================
|
||||
// StatusIndicator - 状态指示器(带脉冲点)
|
||||
// ============================================
|
||||
|
||||
export interface StatusIndicatorProps extends Omit<BoxProps, 'css'> {
|
||||
/** 状态 */
|
||||
status: 'active' | 'inactive' | 'warning' | 'error';
|
||||
/** 标签文字 */
|
||||
label?: string;
|
||||
/** 是否显示脉冲动画 */
|
||||
pulse?: boolean;
|
||||
}
|
||||
|
||||
const STATUS_COLORS = {
|
||||
active: FUI_COLORS.gold[400],
|
||||
inactive: FUI_COLORS.text.muted,
|
||||
warning: FUI_COLORS.status.warning,
|
||||
error: FUI_COLORS.status.positive,
|
||||
};
|
||||
|
||||
export const StatusIndicator: React.FC<StatusIndicatorProps> = memo(({
|
||||
status,
|
||||
label,
|
||||
pulse = true,
|
||||
...props
|
||||
}) => {
|
||||
const color = STATUS_COLORS[status];
|
||||
|
||||
return (
|
||||
<HStack spacing={2} {...props}>
|
||||
<Box position="relative">
|
||||
{/* 脉冲光环 */}
|
||||
{pulse && status === 'active' && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
transform="translate(-50%, -50%)"
|
||||
w="12px"
|
||||
h="12px"
|
||||
borderRadius="full"
|
||||
bg={color}
|
||||
opacity={0.3}
|
||||
sx={{
|
||||
animation: 'pulse 2s ease-in-out infinite',
|
||||
'@keyframes pulse': {
|
||||
'0%, 100%': { transform: 'translate(-50%, -50%) scale(1)', opacity: 0.3 },
|
||||
'50%': { transform: 'translate(-50%, -50%) scale(1.5)', opacity: 0 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* 核心点 */}
|
||||
<Box
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius="full"
|
||||
bg={color}
|
||||
boxShadow={`0 0 8px ${color}`}
|
||||
/>
|
||||
</Box>
|
||||
{label && (
|
||||
<Text fontSize="sm" color={FUI_COLORS.text.secondary}>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
|
||||
StatusIndicator.displayName = 'StatusIndicator';
|
||||
|
||||
// ============================================
|
||||
// FUIDivider - FUI 风格分隔线
|
||||
// ============================================
|
||||
|
||||
export interface FUIDividerProps extends Omit<BoxProps, 'css'> {
|
||||
/** 方向 */
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
/** 是否发光 */
|
||||
glowing?: boolean;
|
||||
/** 中间文字 */
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const FUIDivider: React.FC<FUIDividerProps> = memo(({
|
||||
orientation = 'horizontal',
|
||||
glowing = false,
|
||||
label,
|
||||
...props
|
||||
}) => {
|
||||
if (orientation === 'vertical') {
|
||||
return (
|
||||
<Box
|
||||
w="1px"
|
||||
h="100%"
|
||||
bg={FUI_COLORS.line.default}
|
||||
boxShadow={glowing ? FUI_GLOW.gold.sm : undefined}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
return (
|
||||
<HStack spacing={3} w="100%" {...props}>
|
||||
<Box
|
||||
flex={1}
|
||||
h="1px"
|
||||
bg={`linear-gradient(90deg, transparent, ${FUI_COLORS.line.emphasis})`}
|
||||
/>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={FUI_COLORS.gold[400]}
|
||||
textTransform="uppercase"
|
||||
letterSpacing="wider"
|
||||
fontWeight="medium"
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Box
|
||||
flex={1}
|
||||
h="1px"
|
||||
bg={`linear-gradient(90deg, ${FUI_COLORS.line.emphasis}, transparent)`}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
w="100%"
|
||||
h="1px"
|
||||
bg={`linear-gradient(90deg, transparent 0%, ${FUI_COLORS.line.emphasis} 50%, transparent 100%)`}
|
||||
boxShadow={glowing ? FUI_GLOW.gold.sm : undefined}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
FUIDivider.displayName = 'FUIDivider';
|
||||
|
||||
// ============================================
|
||||
// FUIProgressBar - FUI 风格进度条
|
||||
// ============================================
|
||||
|
||||
export interface FUIProgressBarProps extends Omit<BoxProps, 'css'> {
|
||||
/** 进度值 (0-100) */
|
||||
value: number;
|
||||
/** 颜色主题 */
|
||||
colorScheme?: 'gold' | 'positive' | 'negative' | 'gradient';
|
||||
/** 是否显示发光效果 */
|
||||
glowing?: boolean;
|
||||
/** 高度 */
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
/** 是否显示数值 */
|
||||
showValue?: boolean;
|
||||
}
|
||||
|
||||
const PROGRESS_COLORS = {
|
||||
gold: FUI_COLORS.gold[400],
|
||||
positive: FUI_COLORS.status.positive,
|
||||
negative: FUI_COLORS.status.negative,
|
||||
gradient: `linear-gradient(90deg, ${FUI_COLORS.gold[500]}, ${FUI_COLORS.gold[300]})`,
|
||||
};
|
||||
|
||||
const PROGRESS_SIZES = {
|
||||
sm: '4px',
|
||||
md: '6px',
|
||||
lg: '8px',
|
||||
};
|
||||
|
||||
export const FUIProgressBar: React.FC<FUIProgressBarProps> = memo(({
|
||||
value,
|
||||
colorScheme = 'gold',
|
||||
glowing = false,
|
||||
size = 'md',
|
||||
showValue = false,
|
||||
...props
|
||||
}) => {
|
||||
const clampedValue = Math.min(100, Math.max(0, value));
|
||||
const color = PROGRESS_COLORS[colorScheme];
|
||||
const isGradient = colorScheme === 'gradient';
|
||||
|
||||
return (
|
||||
<HStack spacing={2} w="100%" {...props}>
|
||||
<Box flex={1}>
|
||||
<Box
|
||||
position="relative"
|
||||
w="100%"
|
||||
h={PROGRESS_SIZES[size]}
|
||||
bg={FUI_COLORS.bg.primary}
|
||||
borderRadius="full"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
h="100%"
|
||||
w={`${clampedValue}%`}
|
||||
bg={isGradient ? color : color}
|
||||
background={isGradient ? color : undefined}
|
||||
borderRadius="full"
|
||||
transition={`width ${FUI_ANIMATION.duration.normal} ${FUI_ANIMATION.easing.smooth}`}
|
||||
boxShadow={glowing ? `0 0 10px ${isGradient ? FUI_COLORS.gold[400] : color}` : undefined}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{showValue && (
|
||||
<Text
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
color={FUI_COLORS.gold[400]}
|
||||
minW="40px"
|
||||
textAlign="right"
|
||||
>
|
||||
{clampedValue.toFixed(0)}%
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
|
||||
FUIProgressBar.displayName = 'FUIProgressBar';
|
||||
|
||||
// ============================================
|
||||
// 统一导出
|
||||
// ============================================
|
||||
|
||||
export default {
|
||||
GlowText,
|
||||
DataBadge,
|
||||
StatusIndicator,
|
||||
FUIDivider,
|
||||
FUIProgressBar,
|
||||
};
|
||||
207
src/views/Company/theme/components/GlassCard.tsx
Normal file
207
src/views/Company/theme/components/GlassCard.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* GlassCard - Glassmorphism 风格卡片组件
|
||||
*
|
||||
* 设计特点:
|
||||
* - 毛玻璃背景效果
|
||||
* - 精细边框发光
|
||||
* - 悬停动画
|
||||
* - 可选角落装饰(FUI 风格)
|
||||
*/
|
||||
|
||||
import React, { memo, forwardRef } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { FUI_COLORS, FUI_GLASS, FUI_GLOW, FUI_ANIMATION } from '../fui';
|
||||
|
||||
// ============================================
|
||||
// 类型定义
|
||||
// ============================================
|
||||
|
||||
export interface GlassCardProps extends Omit<BoxProps, 'css'> {
|
||||
children: React.ReactNode;
|
||||
/** 变体样式 */
|
||||
variant?: 'default' | 'elevated' | 'outlined' | 'subtle';
|
||||
/** 是否启用悬停效果 */
|
||||
hoverable?: boolean;
|
||||
/** 是否启用发光效果 */
|
||||
glowing?: boolean;
|
||||
/** 是否显示角落装饰 */
|
||||
cornerDecor?: boolean;
|
||||
/** 是否显示扫描线效果 */
|
||||
scanline?: boolean;
|
||||
/** 边框圆角 */
|
||||
rounded?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
||||
/** 内边距预设 */
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 样式配置
|
||||
// ============================================
|
||||
|
||||
const VARIANTS = {
|
||||
default: {
|
||||
bg: `linear-gradient(135deg, ${FUI_COLORS.bg.elevated} 0%, ${FUI_COLORS.bg.primary} 100%)`,
|
||||
border: FUI_GLASS.border.default,
|
||||
backdropFilter: FUI_GLASS.blur.md,
|
||||
},
|
||||
elevated: {
|
||||
bg: `linear-gradient(145deg, ${FUI_COLORS.bg.surface} 0%, ${FUI_COLORS.bg.elevated} 100%)`,
|
||||
border: FUI_GLASS.border.emphasis,
|
||||
backdropFilter: FUI_GLASS.blur.lg,
|
||||
},
|
||||
outlined: {
|
||||
bg: 'transparent',
|
||||
border: FUI_GLASS.border.emphasis,
|
||||
backdropFilter: 'none',
|
||||
},
|
||||
subtle: {
|
||||
bg: FUI_GLASS.bg.gold,
|
||||
border: FUI_GLASS.border.subtle,
|
||||
backdropFilter: FUI_GLASS.blur.sm,
|
||||
},
|
||||
};
|
||||
|
||||
const ROUNDED_MAP = {
|
||||
sm: '8px',
|
||||
md: '12px',
|
||||
lg: '16px',
|
||||
xl: '20px',
|
||||
'2xl': '24px',
|
||||
};
|
||||
|
||||
const PADDING_MAP = {
|
||||
none: 0,
|
||||
sm: 3,
|
||||
md: 4,
|
||||
lg: 6,
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 角落装饰组件
|
||||
// ============================================
|
||||
|
||||
const CornerDecor: React.FC<{ position: 'tl' | 'tr' | 'bl' | 'br' }> = memo(({ position }) => {
|
||||
const baseStyle = {
|
||||
position: 'absolute' as const,
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderColor: FUI_COLORS.gold[400],
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 0,
|
||||
opacity: 0.6,
|
||||
};
|
||||
|
||||
const positions = {
|
||||
tl: { top: '8px', left: '8px', borderTopWidth: '2px', borderLeftWidth: '2px' },
|
||||
tr: { top: '8px', right: '8px', borderTopWidth: '2px', borderRightWidth: '2px' },
|
||||
bl: { bottom: '8px', left: '8px', borderBottomWidth: '2px', borderLeftWidth: '2px' },
|
||||
br: { bottom: '8px', right: '8px', borderBottomWidth: '2px', borderRightWidth: '2px' },
|
||||
};
|
||||
|
||||
return <Box sx={{ ...baseStyle, ...positions[position] }} />;
|
||||
});
|
||||
|
||||
CornerDecor.displayName = 'CornerDecor';
|
||||
|
||||
// ============================================
|
||||
// 扫描线覆盖层
|
||||
// ============================================
|
||||
|
||||
const ScanlineOverlay: React.FC = memo(() => (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
pointerEvents="none"
|
||||
opacity={0.3}
|
||||
sx={{
|
||||
background: `repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(212, 175, 55, 0.02) 2px,
|
||||
rgba(212, 175, 55, 0.02) 4px
|
||||
)`,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
ScanlineOverlay.displayName = 'ScanlineOverlay';
|
||||
|
||||
// ============================================
|
||||
// 主组件
|
||||
// ============================================
|
||||
|
||||
const GlassCard = forwardRef<HTMLDivElement, GlassCardProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
variant = 'default',
|
||||
hoverable = true,
|
||||
glowing = false,
|
||||
cornerDecor = false,
|
||||
scanline = false,
|
||||
rounded = 'lg',
|
||||
padding = 'md',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const variantStyle = VARIANTS[variant];
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
position="relative"
|
||||
bg={variantStyle.bg}
|
||||
border={variantStyle.border}
|
||||
borderRadius={ROUNDED_MAP[rounded]}
|
||||
backdropFilter={variantStyle.backdropFilter}
|
||||
p={PADDING_MAP[padding]}
|
||||
transition={`all ${FUI_ANIMATION.duration.normal} ${FUI_ANIMATION.easing.smooth}`}
|
||||
overflow="hidden"
|
||||
_hover={
|
||||
hoverable
|
||||
? {
|
||||
borderColor: FUI_COLORS.line.emphasis,
|
||||
boxShadow: glowing ? FUI_GLOW.gold.md : FUI_GLOW.gold.sm,
|
||||
transform: 'translateY(-2px)',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
sx={{
|
||||
// 发光效果
|
||||
...(glowing && {
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
animation: 'glowPulse 3s ease-in-out infinite',
|
||||
}),
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{/* 扫描线效果 */}
|
||||
{scanline && <ScanlineOverlay />}
|
||||
|
||||
{/* 角落装饰 */}
|
||||
{cornerDecor && (
|
||||
<>
|
||||
<CornerDecor position="tl" />
|
||||
<CornerDecor position="tr" />
|
||||
<CornerDecor position="bl" />
|
||||
<CornerDecor position="br" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 内容 */}
|
||||
<Box position="relative" zIndex={1}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
GlassCard.displayName = 'GlassCard';
|
||||
|
||||
export default memo(GlassCard);
|
||||
21
src/views/Company/theme/components/index.ts
Normal file
21
src/views/Company/theme/components/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* FUI 主题组件统一导出
|
||||
*/
|
||||
|
||||
export { default as GlassCard } from './GlassCard';
|
||||
export type { GlassCardProps } from './GlassCard';
|
||||
|
||||
export {
|
||||
GlowText,
|
||||
DataBadge,
|
||||
StatusIndicator,
|
||||
FUIDivider,
|
||||
FUIProgressBar,
|
||||
} from './FUIElements';
|
||||
export type {
|
||||
GlowTextProps,
|
||||
DataBadgeProps,
|
||||
StatusIndicatorProps,
|
||||
FUIDividerProps,
|
||||
FUIProgressBarProps,
|
||||
} from './FUIElements';
|
||||
141
src/views/Company/theme/fui-animations.css
Normal file
141
src/views/Company/theme/fui-animations.css
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* FUI 主题动画样式
|
||||
*
|
||||
* 在 Company 模块的入口文件中引入此文件:
|
||||
* import './theme/fui-animations.css';
|
||||
*/
|
||||
|
||||
/* 发光脉冲动画 */
|
||||
@keyframes glowPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 20px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 30px rgba(212, 175, 55, 0.5), 0 0 60px rgba(212, 175, 55, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 淡入动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 向上滑入动画 */
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 扫描线动画 */
|
||||
@keyframes scanline {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 闪烁动画 */
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 边框发光动画 */
|
||||
@keyframes borderGlow {
|
||||
0%, 100% {
|
||||
border-color: rgba(212, 175, 55, 0.2);
|
||||
}
|
||||
50% {
|
||||
border-color: rgba(212, 175, 55, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 脉冲动画 */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.5);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 数据流动动画 */
|
||||
@keyframes dataFlow {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 旋转发光动画 */
|
||||
@keyframes rotateGlow {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 文字发光动画 */
|
||||
@keyframes textGlow {
|
||||
0%, 100% {
|
||||
text-shadow: 0 0 10px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
50% {
|
||||
text-shadow: 0 0 20px rgba(212, 175, 55, 0.6), 0 0 30px rgba(212, 175, 55, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* 浮动动画 */
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 呼吸动画 */
|
||||
@keyframes breathe {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
336
src/views/Company/theme/fui.ts
Normal file
336
src/views/Company/theme/fui.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* FUI (Fantasy User Interface) 科幻主题配置
|
||||
*
|
||||
* 设计灵感:
|
||||
* - Ash Thorp: 精细线条、数据可视化、霓虹发光
|
||||
* - Linear.app: 极简、精致微交互、清晰层次
|
||||
* - James Turrell: 柔和光影渐变、沉浸式氛围
|
||||
* - HeroUI: 现代组件风格
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 核心色彩系统
|
||||
// ============================================
|
||||
|
||||
export const FUI_COLORS = {
|
||||
// 主色调 - 金色系(保持黑金风格)
|
||||
gold: {
|
||||
50: '#FDF8E8',
|
||||
100: '#F8EDCB',
|
||||
200: '#F0D78C',
|
||||
300: '#E8C14D',
|
||||
400: '#D4AF37', // 主金色
|
||||
500: '#B8960C',
|
||||
600: '#8B7209',
|
||||
700: '#5E4D06',
|
||||
800: '#312803',
|
||||
900: '#1A1500',
|
||||
},
|
||||
|
||||
// 环境光色 - James Turrell 光影
|
||||
ambient: {
|
||||
cyan: 'rgba(0, 255, 255, 0.15)',
|
||||
magenta: 'rgba(255, 0, 255, 0.1)',
|
||||
warm: 'rgba(255, 200, 100, 0.08)',
|
||||
cool: 'rgba(100, 200, 255, 0.08)',
|
||||
},
|
||||
|
||||
// 背景层次
|
||||
bg: {
|
||||
deep: '#0A0A14', // 最深层
|
||||
primary: '#0F0F1A', // 主背景
|
||||
elevated: '#1A1A2E', // 抬升层
|
||||
surface: '#252540', // 表面层
|
||||
overlay: 'rgba(26, 26, 46, 0.95)', // 覆盖层
|
||||
},
|
||||
|
||||
// 边框 & 线条
|
||||
line: {
|
||||
subtle: 'rgba(212, 175, 55, 0.1)',
|
||||
default: 'rgba(212, 175, 55, 0.2)',
|
||||
emphasis: 'rgba(212, 175, 55, 0.4)',
|
||||
glow: 'rgba(212, 175, 55, 0.6)',
|
||||
},
|
||||
|
||||
// 文字
|
||||
text: {
|
||||
primary: 'rgba(255, 255, 255, 0.95)',
|
||||
secondary: 'rgba(255, 255, 255, 0.7)',
|
||||
muted: 'rgba(255, 255, 255, 0.5)',
|
||||
dim: 'rgba(255, 255, 255, 0.3)',
|
||||
},
|
||||
|
||||
// 状态色
|
||||
status: {
|
||||
positive: '#EF4444', // 涨 - 红
|
||||
negative: '#22C55E', // 跌 - 绿
|
||||
warning: '#F59E0B',
|
||||
info: '#3B82F6',
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
// 发光效果(Glow Effects)
|
||||
// ============================================
|
||||
|
||||
export const FUI_GLOW = {
|
||||
// 金色发光
|
||||
gold: {
|
||||
sm: '0 0 8px rgba(212, 175, 55, 0.3)',
|
||||
md: '0 0 16px rgba(212, 175, 55, 0.4)',
|
||||
lg: '0 0 32px rgba(212, 175, 55, 0.5)',
|
||||
pulse: '0 0 20px rgba(212, 175, 55, 0.6), 0 0 40px rgba(212, 175, 55, 0.3)',
|
||||
},
|
||||
|
||||
// 环境光发光 (Turrell style)
|
||||
ambient: {
|
||||
warm: '0 0 60px rgba(255, 200, 100, 0.15)',
|
||||
cool: '0 0 60px rgba(100, 200, 255, 0.1)',
|
||||
mixed: '0 0 80px rgba(212, 175, 55, 0.1), 0 0 120px rgba(100, 200, 255, 0.05)',
|
||||
},
|
||||
|
||||
// 文字发光
|
||||
text: {
|
||||
gold: '0 0 10px rgba(212, 175, 55, 0.5)',
|
||||
white: '0 0 10px rgba(255, 255, 255, 0.3)',
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
// Glassmorphism 配置
|
||||
// ============================================
|
||||
|
||||
export const FUI_GLASS = {
|
||||
// 背景模糊
|
||||
blur: {
|
||||
sm: 'blur(8px)',
|
||||
md: 'blur(16px)',
|
||||
lg: 'blur(24px)',
|
||||
xl: 'blur(40px)',
|
||||
},
|
||||
|
||||
// 玻璃背景
|
||||
bg: {
|
||||
light: 'rgba(255, 255, 255, 0.03)',
|
||||
medium: 'rgba(255, 255, 255, 0.05)',
|
||||
dark: 'rgba(0, 0, 0, 0.2)',
|
||||
gold: 'rgba(212, 175, 55, 0.05)',
|
||||
},
|
||||
|
||||
// 边框
|
||||
border: {
|
||||
subtle: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
default: '1px solid rgba(212, 175, 55, 0.15)',
|
||||
emphasis: '1px solid rgba(212, 175, 55, 0.3)',
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
// 动画配置
|
||||
// ============================================
|
||||
|
||||
export const FUI_ANIMATION = {
|
||||
// 过渡时间
|
||||
duration: {
|
||||
instant: '0.1s',
|
||||
fast: '0.2s',
|
||||
normal: '0.3s',
|
||||
slow: '0.5s',
|
||||
slower: '0.8s',
|
||||
},
|
||||
|
||||
// 缓动函数
|
||||
easing: {
|
||||
default: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
smooth: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
|
||||
bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
||||
expo: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
},
|
||||
|
||||
// 预设动画
|
||||
presets: {
|
||||
fadeIn: 'fadeIn 0.3s ease-out',
|
||||
slideUp: 'slideUp 0.3s ease-out',
|
||||
glow: 'glowPulse 2s ease-in-out infinite',
|
||||
scanline: 'scanline 3s linear infinite',
|
||||
shimmer: 'shimmer 2s linear infinite',
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
// CSS Keyframes(需要在全局样式中注入)
|
||||
// ============================================
|
||||
|
||||
export const FUI_KEYFRAMES = `
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes glowPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 20px rgba(212, 175, 55, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 30px rgba(212, 175, 55, 0.5), 0 0 60px rgba(212, 175, 55, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scanline {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
10% { opacity: 1; }
|
||||
90% { opacity: 1; }
|
||||
100% {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes borderGlow {
|
||||
0%, 100% {
|
||||
border-color: rgba(212, 175, 55, 0.2);
|
||||
}
|
||||
50% {
|
||||
border-color: rgba(212, 175, 55, 0.5);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ============================================
|
||||
// 组件样式预设
|
||||
// ============================================
|
||||
|
||||
export const FUI_STYLES = {
|
||||
// Glassmorphism 卡片
|
||||
glassCard: {
|
||||
base: {
|
||||
background: `linear-gradient(135deg, ${FUI_COLORS.bg.elevated} 0%, ${FUI_COLORS.bg.primary} 100%)`,
|
||||
backdropFilter: FUI_GLASS.blur.md,
|
||||
border: FUI_GLASS.border.default,
|
||||
borderRadius: '16px',
|
||||
transition: `all ${FUI_ANIMATION.duration.normal} ${FUI_ANIMATION.easing.smooth}`,
|
||||
},
|
||||
hover: {
|
||||
borderColor: FUI_COLORS.line.emphasis,
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
transform: 'translateY(-2px)',
|
||||
},
|
||||
},
|
||||
|
||||
// FUI 面板(带角落装饰)
|
||||
fuiPanel: {
|
||||
base: {
|
||||
position: 'relative' as const,
|
||||
background: FUI_COLORS.bg.elevated,
|
||||
border: FUI_GLASS.border.default,
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
cornerDecor: {
|
||||
position: 'absolute' as const,
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderColor: FUI_COLORS.gold[400],
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
},
|
||||
|
||||
// 扫描线效果
|
||||
scanlineOverlay: {
|
||||
position: 'absolute' as const,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
pointerEvents: 'none' as const,
|
||||
background: `repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(212, 175, 55, 0.03) 2px,
|
||||
rgba(212, 175, 55, 0.03) 4px
|
||||
)`,
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
// 数据标签
|
||||
dataTag: {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
padding: '4px 12px',
|
||||
background: 'rgba(212, 175, 55, 0.1)',
|
||||
border: '1px solid rgba(212, 175, 55, 0.3)',
|
||||
borderRadius: '20px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
color: FUI_COLORS.gold[400],
|
||||
letterSpacing: '0.5px',
|
||||
},
|
||||
|
||||
// 发光按钮
|
||||
glowButton: {
|
||||
base: {
|
||||
background: `linear-gradient(135deg, ${FUI_COLORS.gold[500]} 0%, ${FUI_COLORS.gold[400]} 100%)`,
|
||||
color: FUI_COLORS.bg.deep,
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '8px',
|
||||
transition: `all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`,
|
||||
},
|
||||
hover: {
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
transform: 'translateY(-1px)',
|
||||
},
|
||||
},
|
||||
|
||||
// 输入框
|
||||
input: {
|
||||
base: {
|
||||
background: FUI_COLORS.bg.primary,
|
||||
border: FUI_GLASS.border.default,
|
||||
borderRadius: '8px',
|
||||
color: FUI_COLORS.text.primary,
|
||||
transition: `all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`,
|
||||
},
|
||||
focus: {
|
||||
borderColor: FUI_COLORS.gold[400],
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================
|
||||
// 导出完整主题对象
|
||||
// ============================================
|
||||
|
||||
export const FUI_THEME = {
|
||||
colors: FUI_COLORS,
|
||||
glow: FUI_GLOW,
|
||||
glass: FUI_GLASS,
|
||||
animation: FUI_ANIMATION,
|
||||
styles: FUI_STYLES,
|
||||
} as const;
|
||||
|
||||
export default FUI_THEME;
|
||||
17
src/views/Company/theme/index.ts
Normal file
17
src/views/Company/theme/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Company 页面 FUI 主题统一导出
|
||||
*/
|
||||
|
||||
// 主题配置
|
||||
export { default as FUI_THEME } from './fui';
|
||||
export {
|
||||
FUI_COLORS,
|
||||
FUI_GLOW,
|
||||
FUI_GLASS,
|
||||
FUI_ANIMATION,
|
||||
FUI_KEYFRAMES,
|
||||
FUI_STYLES,
|
||||
} from './fui';
|
||||
|
||||
// 主题组件
|
||||
export * from './components';
|
||||
Reference in New Issue
Block a user