diff --git a/src/pages/cnb-board/components/DashCard.tsx b/src/pages/cnb-board/components/DashCard.tsx new file mode 100644 index 0000000..51e772c --- /dev/null +++ b/src/pages/cnb-board/components/DashCard.tsx @@ -0,0 +1,116 @@ +import { ExternalLink, LayoutDashboard, ArrowUpRight } from 'lucide-react'; +import { useMemo, useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { ChevronDown, ChevronUp } from 'lucide-react'; +import { useNavigate } from '@tanstack/react-router'; + +interface InfoItem { + title: string; + value: string; + description: string; +} + +interface DashCardProps { + isCNB: boolean; + liveData: { list?: InfoItem[] } | null; +} + +const DEFAULT_SHOW_COUNT = 12; + +function isUrl(value: string): boolean { + try { + const url = new URL(value); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +} + +export function DashCard({ isCNB, liveData }: DashCardProps) { + const [expanded, setExpanded] = useState(false); + if (!isCNB || !liveData?.list?.length) return null; + const data = useMemo(() => { + if (!liveData?.list) return { docs: null, list: [] }; + const docs = liveData.list.find((item) => item.title === 'docs'); + const list = liveData.list.filter((item) => item.title !== 'docs'); + return { docs, list } + }, [liveData]); + + const showList = expanded ? data.list : data.list.slice(0, DEFAULT_SHOW_COUNT); + const hasMore = data.list.length > DEFAULT_SHOW_COUNT; + + return ( +
+
+ {showList.map((item) => ( +
+
{item.title}
+ {item.value ? ( + isUrl(item.value) ? ( + + {item.value} + + + ) : ( +
{item.value}
+ ) + ) : ( +
(空)
+ )} + {item.description && ( +
+ {item.description} +
+ )} +
+ ))} +
+ {hasMore && ( +
+ +
+ )} +
+ ); +} + +export const DashTitle = () => { + const navigate = useNavigate(); + const toCNBBoard = () => { + navigate({ to: '/cnb-board' }); + } + return ( +
+ + CNB Board 实时信息 + +
+ ); +} \ No newline at end of file diff --git a/src/pages/cnb-board/page.tsx b/src/pages/cnb-board/page.tsx index 2119045..c4eb4c2 100644 --- a/src/pages/cnb-board/page.tsx +++ b/src/pages/cnb-board/page.tsx @@ -1,6 +1,7 @@ import { useEffect, useState, useCallback, useMemo } from 'react'; import { useCnbBoardStore } from './store'; import { InfoCard } from './components/InfoCard'; + import { Button } from '@/components/ui/button'; import { RotateCcw } from 'lucide-react'; import { useLocation } from '@tanstack/react-router'; @@ -8,12 +9,12 @@ import { useLocation } from '@tanstack/react-router'; type TabValue = 'build' | 'repo' | 'pull' | 'npc' | 'comment' | 'content'; const tabs: { value: TabValue; label: string }[] = [ + { value: 'content', label: '首要信息' }, { value: 'build', label: '构建信息' }, { value: 'repo', label: '仓库信息' }, { value: 'pull', label: 'PR 信息' }, { value: 'npc', label: 'NPC 信息' }, { value: 'comment', label: '评论信息' }, - { value: 'content', label: 'MD 内容' }, ]; export const App = () => { @@ -25,7 +26,7 @@ export const App = () => { const urlTab = searchParams.get('tab') as TabValue | null; const [activeTab, setActiveTab] = useState( - urlTab && tabs.some(t => t.value === urlTab) ? urlTab : 'build' + urlTab && tabs.some(t => t.value === urlTab) ? urlTab : 'content' ); useEffect(() => { @@ -42,10 +43,10 @@ export const App = () => { const currentData = activeTab === 'build' ? data['live_build_info'] : activeTab === 'repo' ? data['live_repo_info'] - : activeTab === 'pull' ? data['live_pull_info'] - : activeTab === 'npc' ? data['live_npc_info'] - : activeTab === 'comment' ? data['live_comment_info'] - : data.live; + : activeTab === 'pull' ? data['live_pull_info'] + : activeTab === 'npc' ? data['live_npc_info'] + : activeTab === 'comment' ? data['live_comment_info'] + : data.live; return (
diff --git a/src/pages/cnb-board/store.ts b/src/pages/cnb-board/store.ts index 333d234..3d68411 100644 --- a/src/pages/cnb-board/store.ts +++ b/src/pages/cnb-board/store.ts @@ -25,12 +25,15 @@ interface CnbBoardData { type State = { loading: boolean; data: CnbBoardData; - fetchAllData: () => Promise; + fetchAllData: (opts?: { all?: boolean }) => Promise; refresh: () => Promise; + isCNB: boolean; + docs: InfoItem | null; }; export const useCnbBoardStore = create((set, get) => ({ loading: false, + isCNB: false, data: { live: null, 'live_repo_info': null, @@ -39,23 +42,29 @@ export const useCnbBoardStore = create((set, get) => ({ 'live_npc_info': null, 'live_comment_info': null, }, - - fetchAllData: async () => { + docs: null, + fetchAllData: async (opts?: { all?: boolean }) => { + const all = opts?.all ?? true; // 1. 先优先加载 live 数据 set({ loading: true }); try { const live = await queryApi['cnb_board']['live']({ more: false }) as Result; + const vscodeWebUrl = live.data?.list?.find?.(item => item.title === 'vscodeWebUrl')?.value || ''; + const isCNB = !!vscodeWebUrl; + const docs = live.data?.list?.find?.(item => item.title === 'docs') || null; set({ loading: false, + docs, data: { ...get().data, live: live?.data || null, }, + isCNB, }); } catch { set({ loading: false }); } - + if (!all) return; // 如果只需要加载 live 数据,直接返回 // 2. 再并行加载其他数据 try { const [liveRepoInfo, liveBuildInfo, livePullInfo, liveNpcInfo, liveCommentInfo] = diff --git a/src/pages/home/components/IntroModule.tsx b/src/pages/home/components/IntroModule.tsx new file mode 100644 index 0000000..906a8f6 --- /dev/null +++ b/src/pages/home/components/IntroModule.tsx @@ -0,0 +1,13 @@ +export const IntroModule = (props: { open?: boolean, title?: React.ReactNode, children?: React.ReactNode }) => { + const { open = false, title, children } = props; + if (!open) return null; + const isString = typeof title === 'string'; + return ( +
+ {title && isString &&

{title}

} + {title && !isString &&
{title}
} + {children} +
+ ); +} \ No newline at end of file diff --git a/src/pages/home/page.tsx b/src/pages/home/page.tsx new file mode 100644 index 0000000..89ab990 --- /dev/null +++ b/src/pages/home/page.tsx @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { DashCard, DashTitle } from '../cnb-board/components/DashCard'; +import { useCnbBoardStore } from '../cnb-board/store'; +import { useShallow } from 'zustand/shallow'; +import { IntroModule } from './components/IntroModule'; +export const App = () => { + const cnbBoard = useCnbBoardStore(useShallow((state) => ({ + isCNB: state.isCNB, + liveData: state.data.live, + fetchAllData: state.fetchAllData, + }))); + useEffect(() => { + cnbBoard.fetchAllData({ all: false }); + }, [cnbBoard.fetchAllData]); + return ( +
+

欢迎来到 Kevisual CLI Center

+

+ 这是一个用于管理云开发, 构建, 工具, 可视化等相关功能的中心化平台。您可以在这里轻松访问和使用各种工具和服务,提升您的开发效率和体验。 +

+
+ }> + + +
+
+ ) +} \ No newline at end of file diff --git a/src/pages/page.tsx b/src/pages/page.tsx index 57f843a..597876b 100644 --- a/src/pages/page.tsx +++ b/src/pages/page.tsx @@ -1,3 +1,3 @@ -import { App } from './cnb-board/page' +import { App } from './home/page' export default App; \ No newline at end of file