更新Company页面的UI为FUI风格
This commit is contained in:
@@ -56,16 +56,16 @@ export interface SubTabTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预设主题
|
* 预设主题 - FUI 风格优化
|
||||||
*/
|
*/
|
||||||
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
const THEME_PRESETS: Record<string, SubTabTheme> = {
|
||||||
blackGold: {
|
blackGold: {
|
||||||
bg: 'gray.900',
|
bg: 'transparent',
|
||||||
borderColor: 'rgba(212, 175, 55, 0.3)',
|
borderColor: 'rgba(212, 175, 55, 0.2)',
|
||||||
tabSelectedBg: '#D4AF37',
|
tabSelectedBg: 'linear-gradient(135deg, #D4AF37 0%, #B8960C 100%)',
|
||||||
tabSelectedColor: 'gray.900',
|
tabSelectedColor: '#0A0A14',
|
||||||
tabUnselectedColor: '#D4AF37',
|
tabUnselectedColor: 'rgba(212, 175, 55, 0.8)',
|
||||||
tabHoverBg: 'gray.600',
|
tabHoverBg: 'rgba(212, 175, 55, 0.1)',
|
||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
bg: 'white',
|
bg: 'white',
|
||||||
@@ -179,23 +179,28 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
key={tab.key}
|
key={tab.key}
|
||||||
color={theme.tabUnselectedColor}
|
color={theme.tabUnselectedColor}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
px={2.5}
|
px={3}
|
||||||
py={1.5}
|
py={1.5}
|
||||||
fontSize="xs"
|
fontSize="xs"
|
||||||
whiteSpace="nowrap"
|
whiteSpace="nowrap"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
|
border="1px solid transparent"
|
||||||
|
transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
|
||||||
_selected={{
|
_selected={{
|
||||||
bg: theme.tabSelectedBg,
|
bg: theme.tabSelectedBg,
|
||||||
color: theme.tabSelectedColor,
|
color: theme.tabSelectedColor,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
|
boxShadow: '0 0 12px rgba(212, 175, 55, 0.4)',
|
||||||
|
border: '1px solid rgba(212, 175, 55, 0.5)',
|
||||||
}}
|
}}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: theme.tabHoverBg,
|
bg: theme.tabHoverBg,
|
||||||
|
border: '1px solid rgba(212, 175, 55, 0.3)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1.5}>
|
||||||
{tab.icon && <Icon as={tab.icon} boxSize={3} />}
|
{tab.icon && <Icon as={tab.icon} boxSize={3.5} />}
|
||||||
<Text>{tab.name}</Text>
|
<Text letterSpacing="wide">{tab.name}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Company 页面顶部搜索栏组件
|
* Company 页面顶部搜索栏组件 - FUI 科幻风格
|
||||||
* - 显示股票代码、名称、价格、涨跌幅
|
*
|
||||||
* - 股票搜索功能
|
* 设计特点:
|
||||||
* - 自选股操作
|
* - Glassmorphism 毛玻璃背景
|
||||||
|
* - 发光效果和微动画
|
||||||
|
* - Ash Thorp 风格的数据展示
|
||||||
|
* - James Turrell 柔和光影
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useMemo, useCallback, useState } from 'react';
|
import React, { memo, useMemo, useCallback, useState } from 'react';
|
||||||
@@ -14,17 +17,24 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Button,
|
Button,
|
||||||
Icon,
|
Icon,
|
||||||
Badge,
|
|
||||||
Skeleton,
|
Skeleton,
|
||||||
|
keyframes,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { AutoComplete, Spin } from 'antd';
|
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 { useStockSearch } from '@hooks/useStockSearch';
|
||||||
import { THEME, getSearchBoxStyles } from '../../config';
|
import { THEME, getSearchBoxStyles } from '../../config';
|
||||||
|
import { FUI_COLORS, FUI_GLOW, FUI_ANIMATION, FUI_GLASS } from '../../theme/fui';
|
||||||
import type { CompanyHeaderProps, StockSearchResult } from '../../types';
|
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<{
|
const StockInfoDisplay = memo<{
|
||||||
stockCode: string;
|
stockCode: string;
|
||||||
@@ -35,47 +45,95 @@ const StockInfoDisplay = memo<{
|
|||||||
}>(({ stockCode, stockName, price, change, loading }) => {
|
}>(({ stockCode, stockName, price, change, loading }) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<VStack align="start" spacing={1}>
|
<VStack align="start" spacing={2}>
|
||||||
<Skeleton height="32px" width="200px" />
|
<Skeleton height="36px" width="220px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||||
<Skeleton height="24px" width="150px" />
|
<Skeleton height="28px" width="160px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPositive = change !== null && change !== undefined && change >= 0;
|
||||||
|
const TrendIcon = isPositive ? TrendingUp : TrendingDown;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack align="start" spacing={0}>
|
<VStack align="start" spacing={1}>
|
||||||
<HStack spacing={2}>
|
{/* 股票代码 & 名称 */}
|
||||||
|
<HStack spacing={3} align="baseline">
|
||||||
<Text
|
<Text
|
||||||
fontSize="2xl"
|
fontSize="2xl"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={THEME.gold}
|
color={FUI_COLORS.gold[400]}
|
||||||
letterSpacing="wider"
|
letterSpacing="wider"
|
||||||
|
textShadow={FUI_GLOW.text.gold}
|
||||||
>
|
>
|
||||||
{stockCode}
|
{stockCode}
|
||||||
</Text>
|
</Text>
|
||||||
{stockName && (
|
{stockName && (
|
||||||
<Text fontSize="xl" fontWeight="medium" color={THEME.textPrimary}>
|
<Text
|
||||||
|
fontSize="xl"
|
||||||
|
fontWeight="medium"
|
||||||
|
color={FUI_COLORS.text.primary}
|
||||||
|
letterSpacing="wide"
|
||||||
|
>
|
||||||
{stockName}
|
{stockName}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
|
{/* 价格 & 涨跌幅 */}
|
||||||
{price !== null && price !== undefined && (
|
{price !== null && price !== undefined && (
|
||||||
<HStack spacing={3} mt={1}>
|
<HStack spacing={4} mt={1}>
|
||||||
<Text fontSize="lg" fontWeight="bold" color={THEME.textPrimary}>
|
{/* 价格 */}
|
||||||
|
<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)}
|
¥{price.toFixed(2)}
|
||||||
</Text>
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 涨跌幅 Badge */}
|
||||||
{change !== null && change !== undefined && (
|
{change !== null && change !== undefined && (
|
||||||
<Badge
|
<Box
|
||||||
px={2}
|
display="inline-flex"
|
||||||
py={0.5}
|
alignItems="center"
|
||||||
borderRadius="md"
|
gap={1}
|
||||||
bg={change >= 0 ? THEME.positiveBg : THEME.negativeBg}
|
px={3}
|
||||||
color={change >= 0 ? THEME.positive : THEME.negative}
|
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"
|
fontSize="sm"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
|
fontFamily="mono"
|
||||||
|
color={isPositive ? FUI_COLORS.status.positive : FUI_COLORS.status.negative}
|
||||||
>
|
>
|
||||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
{isPositive ? '+' : ''}{change.toFixed(2)}%
|
||||||
</Badge>
|
</Text>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
@@ -154,8 +212,36 @@ const SearchActions = memo<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack spacing={3}>
|
<HStack spacing={3}>
|
||||||
{/* 搜索框 */}
|
{/* 搜索框 - FUI 风格 */}
|
||||||
<Box sx={getSearchBoxStyles(THEME)}>
|
<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
|
<AutoComplete
|
||||||
value={inputCode}
|
value={inputCode}
|
||||||
options={stockOptions}
|
options={stockOptions}
|
||||||
@@ -163,36 +249,57 @@ const SearchActions = memo<{
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
placeholder="输入代码、名称或拼音"
|
placeholder="输入代码、名称或拼音"
|
||||||
style={{ width: 220 }}
|
style={{ width: 240 }}
|
||||||
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 搜索按钮 */}
|
{/* 搜索按钮 - 发光效果 */}
|
||||||
<Button
|
<Button
|
||||||
bg={THEME.gold}
|
bg={`linear-gradient(135deg, ${FUI_COLORS.gold[500]} 0%, ${FUI_COLORS.gold[400]} 100%)`}
|
||||||
color={THEME.bg}
|
color={FUI_COLORS.bg.deep}
|
||||||
_hover={{ bg: THEME.goldLight }}
|
_hover={{
|
||||||
_active={{ bg: THEME.goldDark }}
|
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"
|
size="md"
|
||||||
|
h="42px"
|
||||||
|
px={5}
|
||||||
onClick={onSearch}
|
onClick={onSearch}
|
||||||
leftIcon={<Icon as={Search} boxSize={4} />}
|
leftIcon={<Icon as={Search} boxSize={4} />}
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
|
borderRadius="10px"
|
||||||
|
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||||
>
|
>
|
||||||
查询
|
查询
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* 自选按钮 */}
|
{/* 自选按钮 - FUI 风格 */}
|
||||||
<Button
|
<Button
|
||||||
variant={isInWatchlist ? 'solid' : 'outline'}
|
variant="outline"
|
||||||
bg={isInWatchlist ? THEME.gold : 'transparent'}
|
bg={isInWatchlist ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||||
color={isInWatchlist ? THEME.bg : THEME.gold}
|
color={FUI_COLORS.gold[400]}
|
||||||
borderColor={THEME.gold}
|
borderColor={isInWatchlist ? FUI_COLORS.gold[400] : FUI_COLORS.line.emphasis}
|
||||||
|
borderWidth="1px"
|
||||||
_hover={{
|
_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"
|
size="md"
|
||||||
|
h="42px"
|
||||||
|
px={5}
|
||||||
onClick={onWatchlistToggle}
|
onClick={onWatchlistToggle}
|
||||||
isLoading={watchlistLoading}
|
isLoading={watchlistLoading}
|
||||||
leftIcon={
|
leftIcon={
|
||||||
@@ -200,9 +307,13 @@ const SearchActions = memo<{
|
|||||||
as={Star}
|
as={Star}
|
||||||
boxSize={4}
|
boxSize={4}
|
||||||
fill={isInWatchlist ? 'currentColor' : 'none'}
|
fill={isInWatchlist ? 'currentColor' : 'none'}
|
||||||
|
filter={isInWatchlist ? 'drop-shadow(0 0 4px rgba(212, 175, 55, 0.6))' : 'none'}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
fontWeight="bold"
|
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 ? '已自选' : '自选'}
|
{isInWatchlist ? '已自选' : '自选'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -248,13 +359,42 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
bg={THEME.cardBg}
|
position="relative"
|
||||||
|
bg={`linear-gradient(180deg, ${FUI_COLORS.bg.elevated} 0%, ${FUI_COLORS.bg.primary} 100%)`}
|
||||||
borderBottom="1px solid"
|
borderBottom="1px solid"
|
||||||
borderColor={THEME.border}
|
borderColor={FUI_COLORS.line.default}
|
||||||
px={6}
|
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
|
<Flex
|
||||||
|
position="relative"
|
||||||
|
zIndex={1}
|
||||||
maxW="container.xl"
|
maxW="container.xl"
|
||||||
mx="auto"
|
mx="auto"
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
|
|||||||
@@ -382,22 +382,6 @@ export const getMinuteKLineOption = (theme: Theme, minuteData: MinuteData | null
|
|||||||
end: 100,
|
end: 100,
|
||||||
minValueSpan: 20,
|
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: [
|
series: [
|
||||||
{
|
{
|
||||||
@@ -811,24 +795,6 @@ export const getMinuteKLineDarkGoldOption = (minuteData: MinuteData | null): ECh
|
|||||||
end: 100,
|
end: 100,
|
||||||
minValueSpan: 20,
|
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: [
|
series: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* 公司详情页面
|
* 公司详情页面 - FUI 科幻风格
|
||||||
*
|
*
|
||||||
* 特性:
|
* 特性:
|
||||||
* - 黑金主题设计
|
* - Ash Thorp 风格 FUI 设计
|
||||||
* - 懒加载 Tab 内容
|
* - James Turrell 光影效果
|
||||||
* - memo 性能优化
|
* - Glassmorphism 毛玻璃卡片
|
||||||
* - axios 数据请求
|
* - Linear.app 风格微交互
|
||||||
|
* - HeroUI 现代组件风格
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, useCallback, useRef, useEffect, Suspense } from 'react';
|
import React, { memo, useCallback, useRef, useEffect, Suspense } from 'react';
|
||||||
|
|
||||||
|
// FUI 动画样式
|
||||||
|
import './theme/fui-animations.css';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Box, Spinner, Center } from '@chakra-ui/react';
|
import { Box, Spinner, Center } from '@chakra-ui/react';
|
||||||
import SubTabContainer from '@components/SubTabContainer';
|
import SubTabContainer from '@components/SubTabContainer';
|
||||||
@@ -30,7 +34,7 @@ const TabLoadingFallback = memo(() => (
|
|||||||
TabLoadingFallback.displayName = 'TabLoadingFallback';
|
TabLoadingFallback.displayName = 'TabLoadingFallback';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 主内容区组件
|
// 主内容区组件 - FUI 风格
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
interface CompanyContentProps {
|
interface CompanyContentProps {
|
||||||
@@ -41,12 +45,61 @@ interface CompanyContentProps {
|
|||||||
const CompanyContent = memo<CompanyContentProps>(({ stockCode, onTabChange }) => (
|
const CompanyContent = memo<CompanyContentProps>(({ stockCode, onTabChange }) => (
|
||||||
<Box maxW="container.xl" mx="auto" px={4} py={6}>
|
<Box maxW="container.xl" mx="auto" px={4} py={6}>
|
||||||
<Box
|
<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"
|
borderRadius="xl"
|
||||||
border="1px solid"
|
border="1px solid"
|
||||||
borderColor={THEME.border}
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
overflow="hidden"
|
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 />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<SubTabContainer
|
<SubTabContainer
|
||||||
tabs={TAB_CONFIG}
|
tabs={TAB_CONFIG}
|
||||||
@@ -128,8 +181,30 @@ const CompanyIndex: React.FC = () => {
|
|||||||
}, [trackTabChanged]);
|
}, [trackTabChanged]);
|
||||||
|
|
||||||
return (
|
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
|
<CompanyHeader
|
||||||
stockCode={stockCode}
|
stockCode={stockCode}
|
||||||
stockInfo={stockInfo}
|
stockInfo={stockInfo}
|
||||||
@@ -139,13 +214,16 @@ const CompanyIndex: React.FC = () => {
|
|||||||
onStockChange={handleStockChange}
|
onStockChange={handleStockChange}
|
||||||
onWatchlistToggle={handleWatchlistToggle}
|
onWatchlistToggle={handleWatchlistToggle}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* 主内容区 */}
|
{/* 主内容区 */}
|
||||||
|
<Box position="relative" zIndex={1}>
|
||||||
<CompanyContent
|
<CompanyContent
|
||||||
stockCode={stockCode}
|
stockCode={stockCode}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</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