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 && (
+
+ setExpanded(!expanded)}>
+ {expanded ? (
+ <>
+
+ 收起
+ >
+ ) : (
+ <>
+
+ 更多 ({data.list.length - DEFAULT_SHOW_COUNT})
+ >
+ )}
+
+
+ )}
+
+ );
+}
+
+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