diff --git a/frontend/app/(main)/layout.tsx b/frontend/app/(main)/layout.tsx index bf61ff86..7a5039ae 100644 --- a/frontend/app/(main)/layout.tsx +++ b/frontend/app/(main)/layout.tsx @@ -15,7 +15,7 @@ export default function ProjectLayout({
-
+
{children}
diff --git a/frontend/app/(main)/project/page.tsx b/frontend/app/(main)/project/page.tsx index 6b03be73..eca50570 100644 --- a/frontend/app/(main)/project/page.tsx +++ b/frontend/app/(main)/project/page.tsx @@ -8,7 +8,7 @@ export const metadata: Metadata = { export default function ProjectPage() { return ( -
+
diff --git a/frontend/app/(main)/settings/payment/page.tsx b/frontend/app/(main)/settings/payment/page.tsx deleted file mode 100644 index 3bf29e95..00000000 --- a/frontend/app/(main)/settings/payment/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {PaymentSettingsPage} from '@/components/common/payment'; - -export default function Page() { - return ( -
- -
- ); -} diff --git a/frontend/components/animate-ui/radix/dialog.tsx b/frontend/components/animate-ui/radix/dialog.tsx index 2e7a71e0..f2a5cdb4 100644 --- a/frontend/components/animate-ui/radix/dialog.tsx +++ b/frontend/components/animate-ui/radix/dialog.tsx @@ -99,6 +99,7 @@ type DialogContentProps = React.ComponentProps & HTMLMotionProps<'div'> & { from?: FlipDirection; transition?: Transition; + showCloseButton?: boolean; }; function DialogContent({ @@ -106,6 +107,7 @@ function DialogContent({ children, from = 'top', transition = {type: 'spring', stiffness: 200, damping: 30}, + showCloseButton = true, ...props }: DialogContentProps) { const {isOpen} = useDialog(); @@ -155,10 +157,12 @@ function DialogContent({ {...props} > {children} - - - Close - + {showCloseButton && ( + + + Close + + )} diff --git a/frontend/components/animate-ui/radix/tabs.tsx b/frontend/components/animate-ui/radix/tabs.tsx index 251e4846..e74fe85a 100644 --- a/frontend/components/animate-ui/radix/tabs.tsx +++ b/frontend/components/animate-ui/radix/tabs.tsx @@ -10,15 +10,27 @@ import { MotionHighlightItem, } from '@/components/animate-ui/effects/motion-highlight'; -type TabsProps = React.ComponentProps; +type TabsVariant = 'default' | 'pill' | 'fill'; -function Tabs({className, ...props}: TabsProps) { +type TabsContextValue = { + variant: TabsVariant; +}; + +const TabsContext = React.createContext({variant: 'default'}); + +type TabsProps = React.ComponentProps & { + variant?: TabsVariant; +}; + +function Tabs({className, variant = 'default', ...props}: TabsProps) { return ( - + + + ); } @@ -39,6 +51,7 @@ function TabsList({ }, ...props }: TabsListProps) { + const {variant} = React.useContext(TabsContext); const localRef = React.useRef(null); React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement); @@ -76,7 +89,14 @@ function TabsList({ return ( @@ -84,7 +104,11 @@ function TabsList({ ref={localRef} data-slot="tabs-list" className={cn( - 'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]', + variant === 'fill' ? + 'inline-flex h-auto w-fit items-center justify-center gap-3 bg-transparent p-0 text-muted-foreground' : + variant === 'pill' ? + 'inline-flex h-auto w-fit items-center justify-center gap-1 rounded-full bg-muted p-1 text-muted-foreground' : + 'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]', className, )} {...props} @@ -98,12 +122,17 @@ function TabsList({ type TabsTriggerProps = React.ComponentProps; function TabsTrigger({className, value, ...props}: TabsTriggerProps) { + const {variant} = React.useContext(TabsContext); return ( + {icon && ( +
+
+ {icon} +
+
+ )} + + 暂无数据 + +
+ ); +} diff --git a/frontend/components/common/dashboard/DashboardMain.tsx b/frontend/components/common/dashboard/DashboardMain.tsx index 93a51453..ee8e4b46 100644 --- a/frontend/components/common/dashboard/DashboardMain.tsx +++ b/frontend/components/common/dashboard/DashboardMain.tsx @@ -3,7 +3,21 @@ import {motion} from 'motion/react'; import {useState} from 'react'; import {StatCard, CardList, UserGrowthChart, ActivityChart, CategoryChart, DistributeModeChart} from '@/components/common/dashboard/'; -import {UsersIcon, DownloadIcon, FolderIcon, TrendingUpIcon, ChartPieIcon, ChartColumnBigIcon, ChartAreaIcon, ChartLineIcon, FlameIcon} from 'lucide-react'; +import {Tabs, TabsList, TabsTrigger} from '@/components/animate-ui/radix/tabs'; +import { + UsersIcon, + DownloadIcon, + FolderIcon, + TrendingUpIcon, + ChartColumnBigIcon, + ChartAreaIcon, + ChartLineIcon, + Flame, + TrendingUpDown, + ChartPie, + LayersPlus, + HandCoins, +} from 'lucide-react'; import {useDashboard} from '@/hooks/use-dashboard'; import {useAuth} from '@/hooks/use-auth'; @@ -53,28 +67,28 @@ export function DashboardMain() { { title: '总用户数', value: data?.summary?.totalUsers, - icon: , + icon: , desc: `+${data?.summary?.newUsers || 0} 新用户`, descColor: 'text-green-600 dark:text-green-400', }, { title: '总项目数', value: data?.summary?.totalProjects, - icon: , + icon: , desc: '项目总数', descColor: 'text-muted-foreground', }, { title: '总领取数', value: data?.summary?.totalReceived, - icon: , + icon: , desc: '历史累计', descColor: 'text-muted-foreground', }, { title: '最近领取数', value: data?.summary?.recentReceived, - icon: , + icon: , desc: `最近${range}天`, descColor: 'text-blue-600 dark:text-blue-400', }, @@ -86,19 +100,19 @@ export function DashboardMain() { const listCards = [ { title: '热门项目', - icon: , + icon: , list: data?.hotProjects || [], type: 'project' as const, }, { title: '活跃创建者', - icon: , + icon: , list: data?.activeCreators || [], type: 'creator' as const, }, { title: '活跃领取者', - icon: , + icon: , list: data?.activeReceivers || [], type: 'receiver' as const, }, @@ -125,48 +139,41 @@ export function DashboardMain() { }, }; - return ( {/* 问候语标题和时间选择器 */} - +
-

- 👋 {getTimeGreeting()}好,{user?.username || 'Linux Do User'} +

+ {getTimeGreeting()}好,{user?.username || 'Linux Do User'}

-

平台数据概览和趋势分析

{/* 时间范围选择器 */} -
-
- {timeRangeOptions.map((option) => ( - - ))} -
+
+ setRange(Number(value))} + variant="pill" + > + + {timeRangeOptions.map((option) => ( + + {option.label} + + ))} + +
- - {/* 统计卡片 - 响应式网格 */} {statsCards.map((card) => ( @@ -182,53 +189,47 @@ export function DashboardMain() { {/* 图表区域 - 1x3 网格布局 */} - + {/* 左侧标签页图表 - 2/3 宽度 */} -
-
+
+
{/* 标签页导航 */} -
-
- - - +
+
+
+ +
+
+ 核心趋势 +
+ setActiveTab(value as 'activity' | 'users' | 'tags')} + variant="fill" + className="self-start" + > + + + + 领取趋势 + + + + 用户增长 + + + + 标签分布 + + +
{/* 标签页内容 */} -
+
{activeTab === 'activity' && ( -
+
)} {activeTab === 'users' && ( -
+
)} {activeTab === 'tags' && ( -
+
{/* 右侧饼图 - 1/3 宽度 */} -
-
- {/* 饼图标题 */} -
-
-
- -
-

分发模式统计

-
-
- {/* 饼图内容 */} -
- } - hideHeader={true} - /> -
-
+
+ } + />
{/* 列表卡片区 - 热门项目、活跃创建者、活跃领取者 */} - + {listCards.map((card) => ( -
-
{title}
-
{icon}
-
-
- {numericValue !== null ? ( - - ) : ( - value - )} +
+
+
+
+ {title} +
+
+
+ {icon} +
- {desc && ( -
- {desc} +
+
+
+ {numericValue !== null ? ( + + ) : ( + value || '--' + )} +
+
+ {desc || '\u00a0'} +
- )} +
); } @@ -40,22 +63,80 @@ export function StatCard({title, value, icon, desc, descColor}: StatCardProps) { * 卡片列表组件 */ export function CardList({title, icon, list, type}: Omit) { + const displayedList = (list || []).slice(0, 10); + + const getMetricValue = (item: ListItemData) => { + switch (type) { + case 'project': + return 'receiveCount' in item ? item.receiveCount : 0; + case 'creator': + return 'projectCount' in item ? item.projectCount : 0; + case 'receiver': + return 'receiveCount' in item ? item.receiveCount : 0; + default: + return 0; + } + }; + + const getMetricLabel = () => { + switch (type) { + case 'project': + return '领取'; + case 'creator': + return '项目'; + case 'receiver': + return '领取'; + default: + return ''; + } + }; + + const getMetricTitle = (item: ListItemData) => { + const value = getMetricValue(item); + + switch (type) { + case 'project': + return `领取数: ${value}`; + case 'creator': + return `项目数: ${value}`; + case 'receiver': + return `领取数: ${value}`; + default: + return String(value); + } + }; + /** * 渲染列表项头像或序号 */ - const renderItemAvatar = (item: ListItemData) => { - if ((type === 'creator' || type === 'receiver') && 'avatar' in item && item.avatar) { + const renderItemLeading = (item: ListItemData, index: number) => { + const rank = String(index + 1).padStart(2, '0'); + + if ( + (type === 'creator' || type === 'receiver') && + 'avatar' in item && + item.avatar + ) { return ( - - - - {item.name?.charAt(0)} - - +
+ + {rank} + + + + + {item.name?.charAt(0)} + + +
); } - return null; + return ( +
+ {rank} +
+ ); }; /** @@ -69,11 +150,9 @@ export function CardList({title, icon, list, type}: Omit { if (type === 'project' && 'tags' in item) { if (item.tags && Array.isArray(item.tags) && item.tags.length > 0) { - const displayTags = item.tags.slice(0, 3); - const remainingCount = item.tags.length - displayTags.length; - return displayTags.join('、') + (remainingCount > 0 ? ` +${remainingCount}` : ''); + return item.tags.slice(0, 2); } else { - return '无标签'; + return []; } } return null; @@ -98,21 +177,29 @@ export function CardList({title, icon, list, type}: Omit -
- +
+ {mainText} - {projectTags && ( -
- {projectTags} +
+
+ {projectTags && projectTags.length > 0 ? ( +
+ {projectTags.map((tag) => ( + + {tag} + + ))}
- )} + ) : subText ? ( +

+ {subText} +

+ ) : null}
- {subText && ( -

- {subText} -

- )}
); }; @@ -121,73 +208,55 @@ export function CardList({title, icon, list, type}: Omit { - const getValue = () => { - switch (type) { - case 'project': - return 'receiveCount' in item ? item.receiveCount : ''; - case 'creator': - return 'projectCount' in item ? item.projectCount : ''; - case 'receiver': - return 'receiveCount' in item ? item.receiveCount : ''; - default: - return ''; - } - }; - - const getTitle = () => { - switch (type) { - case 'project': - return 'receiveCount' in item ? `领取数: ${item.receiveCount}` : ''; - case 'creator': - return 'projectCount' in item ? `项目数: ${item.projectCount}` : ''; - case 'receiver': - return 'receiveCount' in item ? `领取数: ${item.receiveCount}` : ''; - default: - return ''; - } - }; + const value = getMetricValue(item); + const label = getMetricLabel(); return (
- {getValue()} +
+ {value} +
+
+ {label} +
); }; return ( -
-
-
-
+
+
+
+
{icon}
-
+
{title}
+
+ {displayedList.length} +
-
-
- {list?.map((item, index) => ( +
+
+ {displayedList.map((item, index) => (
- {renderItemAvatar(item)} + {renderItemLeading(item, index)} {renderItemContent(item)} {renderItemMetric(item)}
))} - {/* 空数据状态 */} - {(!list || list.length === 0) && ( -
- 暂无数据 -
+ {displayedList.length === 0 && ( + )}
@@ -200,7 +269,7 @@ export function CardList({title, icon, list, type}: Omit) { return ( -
+
{icon && ( @@ -217,19 +286,17 @@ export function TagsDisplay({title, tags, icon}: Omit ( {tag.name} {tag.count} )) ) : ( - 暂无标签数据 + } className="h-[160px]" /> )}
); } - - diff --git a/frontend/components/common/dashboard/DataCharts.tsx b/frontend/components/common/dashboard/DataCharts.tsx index 075cdc5c..22b8467d 100644 --- a/frontend/components/common/dashboard/DataCharts.tsx +++ b/frontend/components/common/dashboard/DataCharts.tsx @@ -1,9 +1,80 @@ 'use client'; -import {useMemo} from 'react'; +import {useMemo, useState} from 'react'; import {DISTRIBUTION_MODE_NAMES} from '../project/constants'; import {ChartContainerProps, UserGrowthChartProps, ActivityChartProps, CategoryChartProps, DistributeModeChartProps, TooltipProps} from '@/lib/services/dashboard/types'; -import {AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar, Legend} from 'recharts'; +import {AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar, Legend, CartesianGrid} from 'recharts'; +import {ChartConfig, ChartContainer as UIChartContainer, ChartTooltip, ChartTooltipContent} from '@/components/ui/chart'; +import {DashboardEmptyState} from './DashboardEmptyState'; + +function formatDateTick(value: string): string { + return value.replace('/', '.'); +} + +function formatYAxisTick(value: number | string): string { + return Number(value) === 0 ? '' : String(value); +} + +function getVisibleDateTicks(data: { date: string }[], range: number): string[] { + if (!data.length) { + return []; + } + + if (range <= 7) { + return data.map((item) => item.date); + } + + const step = range <= 15 ? 2 : 3; + const ticks = data + .filter((_, index) => index % step === 0) + .map((item) => item.date); + + const lastTick = data[data.length - 1]?.date; + if (lastTick && !ticks.includes(lastTick)) { + ticks.push(lastTick); + } + + return ticks; +} + +function truncateCategoryTick(value: string, maxLength = 8): string { + if (value.length <= maxLength) { + return value; + } + + return `${value.slice(0, maxLength - 1)}…`; +} + +const SHARED_CHART_COLORS = [ + '#5b84e6', + '#4cad87', + '#c4963f', + '#c86f9d', + '#846fe6', + '#42a9bc', + '#c57d49', +]; + +const USER_GROWTH_CHART_CONFIG = { + value: { + label: '新增用户', + color: 'var(--chart-1)', + }, +} satisfies ChartConfig; + +const ACTIVITY_CHART_CONFIG = { + value: { + label: '领取数', + color: '#10b981', + }, +} satisfies ChartConfig; + +const CATEGORY_CHART_CONFIG = { + value: { + label: '标签热度', + color: 'var(--chart-1)', + }, +} satisfies ChartConfig; /** * 生成时间范围图表数据 @@ -76,37 +147,26 @@ const ANIMATION_CONFIG = { }, area: { animationBegin: 0, - animationDuration: 800, + animationDuration: 1150, }, line: { animationBegin: 100, - animationDuration: 800, + animationDuration: 1100, }, pie: { - animationBegin: 200, - animationDuration: 1000, + animationBegin: 120, + animationDuration: 1350, }, bar: { - animationBegin: 300, - animationDuration: 900, + animationBegin: 160, + animationDuration: 1200, }, }; // 配色方案 const ENHANCED_COLORS = { // 饼图配色 - pieChart: [ - '#6366f1', - '#10b981', - '#f59e0b', - '#ef4444', - '#8b5cf6', - '#06b6d4', - '#84cc16', - '#f97316', - '#ec4899', - '#6b7280', - ], + pieChart: SHARED_CHART_COLORS, // 柱状图配色 barChart: [ '#3b82f6', @@ -121,50 +181,6 @@ const ENHANCED_COLORS = { }, }; -/** - * 工具提示组件 - */ -const EnhancedTooltip = ({active, payload, label}: TooltipProps) => { - if (active && payload && payload.length) { - return ( -
-
- {/* 标题 */} -
- {label} -
- - {/* 数据项 */} -
- {payload.map((entry, index) => { - let itemColor = entry.color; - if (!itemColor) { - itemColor = entry.payload?.color as string | undefined; - } - - return ( -
-
-
- 数量 -
- - {typeof entry.value === 'number' ? entry.value.toLocaleString() : entry.value} - -
- ); - })} -
-
-
- ); - } - return null; -}; - /** * 饼图工具提示 */ @@ -177,9 +193,9 @@ const PieTooltip = ({active, payload}: TooltipProps) => { const correctColor = (data.payload?.color as string) || data.color; return ( -
-
-
+
+
+
{data.name}
@@ -190,9 +206,9 @@ const PieTooltip = ({active, payload}: TooltipProps) => { className="w-1.5 h-1.5 rounded-full" style={{backgroundColor: correctColor as string}} /> - 数量 + 数量
- + {(data.value as number).toLocaleString()}
@@ -202,9 +218,9 @@ const PieTooltip = ({active, payload}: TooltipProps) => { className="w-1.5 h-1.5 rounded-full" style={{backgroundColor: correctColor as string}} /> - 占比 + 占比
- + {percentage}%
@@ -219,26 +235,26 @@ const PieTooltip = ({active, payload}: TooltipProps) => { /** * 图表容器 */ -function ChartContainer({title, icon, isLoading, children, hideHeader = false}: ChartContainerProps & {hideHeader?: boolean}) { +function DashboardChartContainer({title, icon, isLoading, children, hideHeader = false}: ChartContainerProps & {hideHeader?: boolean}) { return ( -
+
{!hideHeader && (
-
+
{icon && ( -
+
{icon}
)} -

{title}

+

{title}

)} -
+
{isLoading ? (
- 数据加载中... + 数据加载中...
) : children}
@@ -251,57 +267,65 @@ function ChartContainer({title, icon, isLoading, children, hideHeader = false}: */ export function UserGrowthChart({data, isLoading, icon, range = 7, hideHeader = false}: UserGrowthChartProps & {hideHeader?: boolean}) { const chartData = useMemo(() => generateTimeRangeChartData(data, range), [data, range]); + const visibleTicks = useMemo(() => getVisibleDateTicks(chartData, range), [chartData, range]); return ( - +
- - + + - - + + + - `日期: ${label}`} />} - cursor={{stroke: '#3b82f6', strokeWidth: 1, strokeDasharray: '3 3', strokeOpacity: 0.6}} + `日期: ${label}`} />} /> - +
-
+ ); } @@ -310,57 +334,65 @@ export function UserGrowthChart({data, isLoading, icon, range = 7, hideHeader = */ export function ActivityChart({data, isLoading, icon, range = 7, hideHeader = false}: ActivityChartProps & {hideHeader?: boolean}) { const chartData = useMemo(() => generateTimeRangeChartData(data, range), [data, range]); + const visibleTicks = useMemo(() => getVisibleDateTicks(chartData, range), [chartData, range]); return ( - +
- - + + - - + + + - `日期: ${label}`} />} - cursor={{stroke: '#10b981', strokeWidth: 1, strokeDasharray: '3 3', strokeOpacity: 0.6}} + `日期: ${label}`} />} /> - +
-
+ ); } @@ -378,65 +410,58 @@ export function CategoryChart({data, isLoading, icon, hideHeader = false}: Categ if (!isLoading && (!chartData || chartData.length === 0)) { return ( - -
-
{icon}
- 暂无标签数据 -
-
+ + + ); } - // 使用映射并添加颜色信息 - const enhancedData = chartData.map((item, index) => ({ - ...item, - color: ENHANCED_COLORS.barChart[index % ENHANCED_COLORS.barChart.length], - })); - return ( - +
- - + + + truncateCategoryTick(String(value))} /> - `标签: ${label}`} />} - cursor={{fill: 'rgba(59, 130, 246, 0.05)'}} + `标签: ${label}`} />} /> - {enhancedData.map((entry, index) => ( + {chartData.map((_, index) => ( ))} - +
-
+ ); } @@ -445,18 +470,16 @@ export function CategoryChart({data, isLoading, icon, hideHeader = false}: Categ * 分发模式统计饼图 */ export function DistributeModeChart({data, isLoading, icon, hideHeader = false}: DistributeModeChartProps & {hideHeader?: boolean}) { + const [activeIndex, setActiveIndex] = useState(null); const chartData = useMemo(() => { return data && data.length > 0 ? data : []; }, [data]); if (!isLoading && (!chartData || chartData.length === 0)) { return ( - -
-
{icon}
- 暂无分发数据 -
-
+ + + ); } @@ -469,19 +492,19 @@ export function DistributeModeChart({data, isLoading, icon, hideHeader = false}: })); return ( - -
+ +
- + { - const target = e.target as HTMLElement; - target.style.transform = 'scale(1.05)'; - target.style.filter = 'drop-shadow(0 4px 8px rgba(0,0,0,0.2))'; - }} - onMouseLeave={(e) => { - const target = e.target as HTMLElement; - target.style.transform = 'scale(1)'; - target.style.filter = 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))'; }} + onMouseEnter={() => setActiveIndex(index)} + onMouseLeave={() => setActiveIndex(null)} /> ))} @@ -520,7 +532,7 @@ export function DistributeModeChart({data, isLoading, icon, hideHeader = false}: iconType="circle" wrapperStyle={{ position: 'absolute', - bottom: '-10px', + bottom: '0px', width: '100%', fontSize: '11px', display: 'flex', @@ -545,6 +557,6 @@ export function DistributeModeChart({data, isLoading, icon, hideHeader = false}:
- +
); } diff --git a/frontend/components/common/layout/ManagementBar.tsx b/frontend/components/common/layout/ManagementBar.tsx index 61446b1b..796bc5c1 100644 --- a/frontend/components/common/layout/ManagementBar.tsx +++ b/frontend/components/common/layout/ManagementBar.tsx @@ -5,9 +5,9 @@ import { SendIcon, BarChartIcon, FolderIcon, + FolderGit2, ShoppingBag, PlusCircle, - Github, ExternalLinkIcon, User, LogOutIcon, @@ -19,6 +19,7 @@ import {CreateDialog} from '@/components/common/project/CreateDialog'; import {CountingNumber} from '@/components/animate-ui/text/counting-number'; import {Button} from '@/components/ui/button'; import Link from 'next/link'; +import {PaymentSettingsDialog} from '@/components/common/payment'; import { Dialog, DialogContent, @@ -28,7 +29,6 @@ import { } from '@/components/animate-ui/radix/dialog'; import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar'; import {TrustLevel} from '@/lib/services/core'; -import {DialogClose} from '@/components/ui/dialog'; const IconOptions = { className: 'h-4 w-4', @@ -58,11 +58,25 @@ export function ManagementBar() { const themeUtils = useThemeUtils(); const {user, isLoading, logout} = useAuth(); const [mounted, setMounted] = useState(false); + const [profileOpen, setProfileOpen] = useState(false); + const [paymentOpen, setPaymentOpen] = useState(false); useEffect(() => { setMounted(true); }, []); + useEffect(() => { + const handleOpenPaymentSettings = () => { + setProfileOpen(false); + setPaymentOpen(true); + }; + + window.addEventListener('linux-do-cdk:open-payment-settings', handleOpenPaymentSettings); + return () => { + window.removeEventListener('linux-do-cdk:open-payment-settings', handleOpenPaymentSettings); + }; + }, []); + const handleLogout = () => { logout('/login').catch((error) => { console.error('登出失败:', error); @@ -104,181 +118,185 @@ export function ManagementBar() { title: '个人信息', icon: , customComponent: ( - - -
- -
-
- - - 个人信息 - -
- {!isLoading && user && ( - <> - {/* 用户信息卡片 */} -
- {/* 用户基本信息和登出按钮 */} -
-
- - - CN - -
-
- {user.username} -
- {user.nickname && ( -
- {user.nickname} + <> + + +
+ +
+
+ + + 个人信息 + +
+ {!isLoading && user && ( + <> + {/* 用户信息卡片 */} +
+ {/* 用户基本信息和登出按钮 */} +
+
+ + + CN + +
+
+ {user.username} +
+ {user.nickname && ( +
+ {user.nickname} +
+ )} +
+ {user.trust_level !== undefined ? getTrustLevelText(user.trust_level) : '未知'} + + {user.id}
- )} -
- {user.trust_level !== undefined ? getTrustLevelText(user.trust_level) : '未知'} - - {user.id}
-
- -
+ +
-
+
- {/* 用户分数 */} - {user.score !== undefined && ( -
-

社区分数

-
- - + {/* 用户分数 */} + {user.score !== undefined && ( +
+

社区分数

+
+ + +
-
- )} + )} - - )} + + )} - {/* 主题设置 */} - {mounted && ( + {/* 主题设置 */} + {mounted && ( +
+

主题设置

+
+ +
+
+ )} + + {/* 账户设置 */}
-

主题设置

-
+

账户设置

+
- )} - {/* 账户设置 */} -
-

账户设置

-
- + {/* 快速链接区域 */} +
+

快速链接

+
-
- +
+
-
- 支付设置 - 配置你作为收款商户的 clientID / clientSecret + Linux Do 社区 + + +
+
+ GitHub 仓库 - -
-
- - {/* 快速链接区域 */} -
-

快速链接

-
- -
- -
- Linux Do 社区 - - -
- -
- GitHub 仓库 - - -
- -
- 功能反馈 - - -
- -
- 群组交流 - + +
+ +
+ 功能反馈 + + +
+ +
+ 群组交流 + +
-
-
-

关于 LINUX DO CDK

-
-
版本: 1.1.0
-
构建时间: 2025-09-27
-
LINUX DO CDK 是一个为 Linux Do 社区打造的内容分发工具平台,旨在提供快速、安全、便捷的 CDK 分享服务。平台支持多种分发方式,具备完善的用户权限管理和风险控制机制。
+
+

关于 LINUX DO CDK

+
+
版本: 1.2.3
+
构建时间: 2026-04-21
+
LINUX DO CDK 是一个为 Linux Do 社区打造的内容分发工具平台,旨在提供快速、安全、便捷的 CDK 分享服务。平台支持多种分发方式,具备完善的用户权限管理和风险控制机制。
+
-
- {!isLoading && !user && ( -
+ {!isLoading && !user && ( +
未登录用户 -
- )} -
- -
+
+ )} +
+ + + + ), }, ]; diff --git a/frontend/components/common/markdown/ContentRender.tsx b/frontend/components/common/markdown/ContentRender.tsx index 3234db3a..0c6be110 100644 --- a/frontend/components/common/markdown/ContentRender.tsx +++ b/frontend/components/common/markdown/ContentRender.tsx @@ -114,7 +114,7 @@ const markdownComponents: Components = { } return ( - + {children} ); @@ -174,7 +174,7 @@ const markdownComponents: Components = { return (
{/* 代码块头部:包含语言标识和复制按钮 */} -
+
@@ -203,7 +203,7 @@ const markdownComponents: Components = { console.error('复制失败:', error); } }} - className="flex items-center gap-1.5 px-2 py-1 text-xs bg-gray-300 dark:bg-gray-700 hover:bg-gray-400 dark:hover:bg-gray-600 rounded transition-colors duration-200 text-gray-700 dark:text-gray-300" + className="flex items-center gap-1.5 rounded bg-muted px-2 py-1 text-xs text-muted-foreground transition-colors duration-200 hover:bg-muted/80" title="复制代码" > @@ -216,7 +216,7 @@ const markdownComponents: Components = { {/* 代码内容区域:支持自动换行和语法高亮 */}
 {
             const target = e.target as HTMLImageElement;
             const errorDiv = document.createElement('div');
-            errorDiv.className = 'bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg p-4 text-center text-gray-500 dark:text-gray-400 my-6';
+            errorDiv.className = 'bg-muted border border-border rounded-lg p-4 text-center text-muted-foreground my-6';
             errorDiv.innerHTML = `
               
                 
@@ -294,7 +294,7 @@ const markdownComponents: Components = {
   },
   table: ({children}) => (
     
- +
{children}
@@ -305,12 +305,12 @@ const markdownComponents: Components = { ), tbody: ({children}) => ( - + {children} ), tr: ({children}) => ( - + {children} ), diff --git a/frontend/components/common/markdown/Editor.tsx b/frontend/components/common/markdown/Editor.tsx index f65cf240..5daf90c5 100644 --- a/frontend/components/common/markdown/Editor.tsx +++ b/frontend/components/common/markdown/Editor.tsx @@ -329,15 +329,14 @@ export function MarkdownEditor({ const primaryButtons = getPrimaryButtons(); const secondaryButtons = getSecondaryButtons(); + const editorSurfaceClass = 'bg-muted/55 dark:bg-white/[0.04]'; return ( -
- {/* 工具栏 */} -
-
+
+
+
- {/* 主要按钮 */} {primaryButtons.map((button, index) => ( @@ -357,7 +356,6 @@ export function MarkdownEditor({ ))} - {/* 更多按钮下拉菜单 */} {secondaryButtons.length > 0 && ( @@ -387,16 +385,15 @@ export function MarkdownEditor({
- {/* 模式切换 */}
- setMode(value as 'edit' | 'preview')}> - - - + setMode(value as 'edit' | 'preview')} variant="fill"> + + + 编辑 - - + + 预览 @@ -405,14 +402,12 @@ export function MarkdownEditor({
- {/* 内容区域 */}
{mode === 'edit' ? (
- {/* 行号区域 */}
))} - {/* 填充空行以保持最小高度 */} {displayLineCount < 15 && Array.from({length: 15 - displayLineCount}, (_, i) => (
- {/* 文本编辑区域 */} -
+