diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 8170d79..ccb8c7b 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -5105,6 +5105,9 @@ const SiteConfigComponent = ({
DanmakuApiToken: '87654321',
TMDBApiKey: '',
TMDBProxy: '',
+ PansouApiUrl: '',
+ PansouUsername: '',
+ PansouPassword: '',
EnableComments: false,
EnableRegistration: false,
RegistrationRequireTurnstile: false,
@@ -5189,6 +5192,9 @@ const SiteConfigComponent = ({
DanmakuApiToken: config.SiteConfig.DanmakuApiToken || '87654321',
TMDBApiKey: config.SiteConfig.TMDBApiKey || '',
TMDBProxy: config.SiteConfig.TMDBProxy || '',
+ PansouApiUrl: config.SiteConfig.PansouApiUrl || '',
+ PansouUsername: config.SiteConfig.PansouUsername || '',
+ PansouPassword: config.SiteConfig.PansouPassword || '',
EnableComments: config.SiteConfig.EnableComments || false,
});
}
@@ -5779,6 +5785,87 @@ const SiteConfigComponent = ({
+ {/* Pansou 配置 */}
+
+
+ Pansou 网盘搜索配置
+
+
+ {/* Pansou API 地址 */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ PansouApiUrl: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 配置 Pansou 服务器地址,用于网盘资源搜索。项目地址:{' '}
+
+ https://github.com/fish2018/pansou
+
+
+
+
+ {/* Pansou 账号 */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ PansouUsername: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 如果 Pansou 服务启用了认证功能,需要提供账号密码
+
+
+
+ {/* Pansou 密码 */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ PansouPassword: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 配置账号密码后,系统会自动登录并缓存 Token
+
+
+
+
{/* 评论功能配置 */}
diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts
index 00d78e9..5ca0eb9 100644
--- a/src/app/api/admin/site/route.ts
+++ b/src/app/api/admin/site/route.ts
@@ -43,6 +43,9 @@ export async function POST(request: NextRequest) {
DanmakuApiToken,
TMDBApiKey,
TMDBProxy,
+ PansouApiUrl,
+ PansouUsername,
+ PansouPassword,
EnableComments,
CustomAdFilterCode,
CustomAdFilterVersion,
@@ -76,6 +79,9 @@ export async function POST(request: NextRequest) {
DanmakuApiToken: string;
TMDBApiKey?: string;
TMDBProxy?: string;
+ PansouApiUrl?: string;
+ PansouUsername?: string;
+ PansouPassword?: string;
EnableComments: boolean;
CustomAdFilterCode?: string;
CustomAdFilterVersion?: number;
@@ -163,6 +169,9 @@ export async function POST(request: NextRequest) {
DanmakuApiToken,
TMDBApiKey,
TMDBProxy,
+ PansouApiUrl,
+ PansouUsername,
+ PansouPassword,
EnableComments,
CustomAdFilterCode,
CustomAdFilterVersion,
diff --git a/src/app/api/pansou/search/route.ts b/src/app/api/pansou/search/route.ts
new file mode 100644
index 0000000..7f10dff
--- /dev/null
+++ b/src/app/api/pansou/search/route.ts
@@ -0,0 +1,61 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+
+import { NextRequest, NextResponse } from 'next/server';
+
+import { getConfig } from '@/lib/config';
+import { searchPansou } from '@/lib/pansou.client';
+
+export const runtime = 'nodejs';
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const { keyword } = body;
+
+ if (!keyword) {
+ return NextResponse.json(
+ { error: '关键词不能为空' },
+ { status: 400 }
+ );
+ }
+
+ // 从系统配置中获取 Pansou 配置
+ const config = await getConfig();
+ const apiUrl = config.SiteConfig.PansouApiUrl;
+ const username = config.SiteConfig.PansouUsername;
+ const password = config.SiteConfig.PansouPassword;
+
+ console.log('Pansou 搜索请求:', {
+ keyword,
+ apiUrl: apiUrl ? '已配置' : '未配置',
+ hasAuth: !!(username && password),
+ });
+
+ if (!apiUrl) {
+ return NextResponse.json(
+ { error: '未配置 Pansou API 地址,请在管理面板配置' },
+ { status: 400 }
+ );
+ }
+
+ // 调用 Pansou 搜索
+ const results = await searchPansou(apiUrl, keyword, {
+ username,
+ password,
+ });
+
+ console.log('Pansou 搜索结果:', {
+ total: results.total,
+ hasData: !!results.merged_by_type,
+ types: results.merged_by_type ? Object.keys(results.merged_by_type) : [],
+ });
+
+ return NextResponse.json(results);
+ } catch (error: any) {
+ console.error('Pansou 搜索失败:', error);
+ return NextResponse.json(
+ { error: error.message || '搜索失败' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/server-config/route.ts b/src/app/api/server-config/route.ts
index 0be1393..2701b09 100644
--- a/src/app/api/server-config/route.ts
+++ b/src/app/api/server-config/route.ts
@@ -46,6 +46,10 @@ export async function GET(request: NextRequest) {
EnableOIDCLogin: config.SiteConfig.EnableOIDCLogin || false,
EnableOIDCRegistration: config.SiteConfig.EnableOIDCRegistration || false,
OIDCButtonText: config.SiteConfig.OIDCButtonText || '',
+ SiteConfig: {
+ PansouApiUrl: config.SiteConfig.PansouApiUrl || '',
+ // 不暴露用户名和密码,认证在后端处理
+ },
};
return NextResponse.json(result);
}
diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx
index 049a8a3..ea15678 100644
--- a/src/app/play/page.tsx
+++ b/src/app/play/page.tsx
@@ -2,7 +2,7 @@
'use client';
-import { Heart } from 'lucide-react';
+import { Heart, Search, X } from 'lucide-react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useRef, useState } from 'react';
@@ -48,6 +48,7 @@ import DoubanComments from '@/components/DoubanComments';
import DanmakuFilterSettings from '@/components/DanmakuFilterSettings';
import Toast, { ToastProps } from '@/components/Toast';
import { useEnableComments } from '@/hooks/useEnableComments';
+import PansouSearch from '@/components/PansouSearch';
// 扩展 HTMLVideoElement 类型以支持 hls 属性
declare global {
@@ -96,6 +97,9 @@ function PlayPageClient() {
// 收藏状态
const [favorited, setFavorited] = useState(false);
+ // 网盘搜索弹窗状态
+ const [showPansouDialog, setShowPansouDialog] = useState(false);
+
// 跳过片头片尾配置
const [skipConfig, setSkipConfig] = useState<{
enable: boolean;
@@ -4890,6 +4894,17 @@ function PlayPageClient() {
>
+ {/* 网盘搜索按钮 */}
+
{/* 豆瓣评分显示 */}
{doubanRating && doubanRating.value > 0 && (
@@ -5107,6 +5122,40 @@ function PlayPageClient() {
});
}}
/>
+
+ {/* 网盘搜索弹窗 */}
+ {showPansouDialog && (
+
setShowPansouDialog(false)}
+ >
+
e.stopPropagation()}
+ >
+ {/* 弹窗头部 */}
+
+
+ 搜索网盘资源: {detail?.title || ''}
+
+
+
+
+ {/* 弹窗内容 */}
+
+
+
+ )}
);
}
diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx
index e8166fa..9174fa4 100644
--- a/src/app/search/page.tsx
+++ b/src/app/search/page.tsx
@@ -18,12 +18,17 @@ import PageLayout from '@/components/PageLayout';
import SearchResultFilter, { SearchFilterCategory } from '@/components/SearchResultFilter';
import SearchSuggestions from '@/components/SearchSuggestions';
import VideoCard, { VideoCardHandle } from '@/components/VideoCard';
+import PansouSearch from '@/components/PansouSearch';
function SearchPageClient() {
// 搜索历史
const [searchHistory, setSearchHistory] = useState
([]);
// 返回顶部按钮显示状态
const [showBackToTop, setShowBackToTop] = useState(false);
+ // 选项卡状态: 'video' 或 'pansou'
+ const [activeTab, setActiveTab] = useState<'video' | 'pansou'>('video');
+ // Pansou 搜索触发标志
+ const [triggerPansouSearch, setTriggerPansouSearch] = useState(false);
const router = useRouter();
const searchParams = useSearchParams();
@@ -381,6 +386,14 @@ function SearchPageClient() {
});
}, [aggregatedResults, filterAgg, searchQuery]);
+ // 监听选项卡切换,自动执行搜索
+ useEffect(() => {
+ // 如果切换到网盘搜索选项卡,且有搜索关键词,且已显示结果,则触发搜索
+ if (activeTab === 'pansou' && searchQuery.trim() && showResults) {
+ setTriggerPansouSearch(prev => !prev);
+ }
+ }, [activeTab]);
+
useEffect(() => {
// 无搜索参数时聚焦搜索框
!searchParams.get('q') && document.getElementById('searchInput')?.focus();
@@ -693,8 +706,15 @@ function SearchPageClient() {
setShowResults(true);
setShowSuggestions(false);
- router.push(`/search?q=${encodeURIComponent(trimmed)}`);
- // 其余由 searchParams 变化的 effect 处理
+ // 根据当前选项卡执行不同的搜索
+ if (activeTab === 'video') {
+ // 影视搜索
+ router.push(`/search?q=${encodeURIComponent(trimmed)}`);
+ // 其余由 searchParams 变化的 effect 处理
+ } else if (activeTab === 'pansou') {
+ // 网盘搜索 - 触发搜索
+ setTriggerPansouSearch(prev => !prev); // 切换状态来触发搜索
+ }
};
const handleSuggestionSelect = (suggestion: string) => {
@@ -778,14 +798,41 @@ function SearchPageClient() {
/>
+
+ {/* 选项卡 */}
+
+
+
+
{/* 搜索结果或搜索历史 */}
{showResults ? (
- {/* 标题 */}
-
+ {activeTab === 'video' ? (
+ <>
+ {/* 影视搜索结果 */}
+ {/* 标题 */}
+
搜索结果
{isFromCache ? (
@@ -930,6 +977,21 @@ function SearchPageClient() {
))}
)}
+ >
+ ) : (
+ <>
+ {/* 网盘搜索结果 */}
+
+
+ 网盘搜索结果
+
+
+
+ >
+ )}
) : searchHistory.length > 0 ? (
// 搜索历史
diff --git a/src/components/PansouSearch.tsx b/src/components/PansouSearch.tsx
new file mode 100644
index 0000000..84d3d1d
--- /dev/null
+++ b/src/components/PansouSearch.tsx
@@ -0,0 +1,314 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+'use client';
+
+import { AlertCircle, Copy, ExternalLink, Loader2 } from 'lucide-react';
+import { useEffect, useState } from 'react';
+
+import { PansouLink, PansouSearchResult } from '@/lib/pansou.client';
+
+interface PansouSearchProps {
+ keyword: string;
+ triggerSearch?: boolean; // 触发搜索的标志
+ onError?: (error: string) => void;
+}
+
+// 网盘类型映射
+const CLOUD_TYPE_NAMES: Record
= {
+ baidu: '百度网盘',
+ aliyun: '阿里云盘',
+ quark: '夸克网盘',
+ tianyi: '天翼云盘',
+ uc: 'UC网盘',
+ mobile: '移动云盘',
+ '115': '115网盘',
+ pikpak: 'PikPak',
+ xunlei: '迅雷网盘',
+ '123': '123网盘',
+ magnet: '磁力链接',
+ ed2k: '电驴链接',
+ others: '其他',
+};
+
+// 网盘类型颜色
+const CLOUD_TYPE_COLORS: Record = {
+ baidu: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200',
+ aliyun: 'bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-200',
+ quark: 'bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-200',
+ tianyi: 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-200',
+ uc: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200',
+ mobile: 'bg-pink-100 text-pink-800 dark:bg-pink-900/40 dark:text-pink-200',
+ '115': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-200',
+ pikpak: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/40 dark:text-indigo-200',
+ xunlei: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-900/40 dark:text-cyan-200',
+ '123': 'bg-teal-100 text-teal-800 dark:bg-teal-900/40 dark:text-teal-200',
+ magnet: 'bg-gray-100 text-gray-800 dark:bg-gray-700/40 dark:text-gray-200',
+ ed2k: 'bg-gray-100 text-gray-800 dark:bg-gray-700/40 dark:text-gray-200',
+ others: 'bg-gray-100 text-gray-800 dark:bg-gray-700/40 dark:text-gray-200',
+};
+
+export default function PansouSearch({
+ keyword,
+ triggerSearch,
+ onError,
+}: PansouSearchProps) {
+ const [loading, setLoading] = useState(false);
+ const [results, setResults] = useState(null);
+ const [error, setError] = useState(null);
+ const [copiedUrl, setCopiedUrl] = useState(null);
+ const [selectedType, setSelectedType] = useState('all'); // 'all' 表示显示全部
+
+ useEffect(() => {
+ // 只在 triggerSearch 变化时执行搜索,不响应 keyword 变化
+ if (!triggerSearch) {
+ return;
+ }
+
+ const currentKeyword = keyword.trim();
+ if (!currentKeyword) {
+ return;
+ }
+
+ const searchPansou = async () => {
+ setLoading(true);
+ setError(null);
+ setResults(null);
+
+ try {
+ const response = await fetch('/api/pansou/search', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ keyword: currentKeyword,
+ }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || '搜索失败');
+ }
+
+ const data: PansouSearchResult = await response.json();
+ setResults(data);
+ } catch (err: any) {
+ const errorMsg = err.message || '搜索失败,请检查配置';
+ setError(errorMsg);
+ onError?.(errorMsg);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ searchPansou();
+ }, [triggerSearch, onError]); // 移除 keyword 依赖,只依赖 triggerSearch
+
+ const handleCopy = async (text: string, url: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopiedUrl(url);
+ setTimeout(() => setCopiedUrl(null), 2000);
+ } catch (err) {
+ console.error('复制失败:', err);
+ }
+ };
+
+ const handleOpenLink = (url: string) => {
+ window.open(url, '_blank', 'noopener,noreferrer');
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (!results || results.total === 0 || !results.merged_by_type) {
+ return (
+
+ );
+ }
+
+ const cloudTypes = Object.keys(results.merged_by_type);
+
+ // 过滤显示的网盘类型
+ const filteredCloudTypes = selectedType === 'all'
+ ? cloudTypes
+ : cloudTypes.filter(type => type === selectedType);
+
+ // 计算每种网盘类型的数量
+ const typeStats = cloudTypes.map(type => ({
+ type,
+ count: results.merged_by_type[type]?.length || 0,
+ }));
+
+ return (
+
+ {/* 搜索结果统计 */}
+
+ 找到 {results.total} 个资源
+
+
+ {/* 网盘类型过滤器 */}
+
+
+ {typeStats.map(({ type, count }) => {
+ const typeName = CLOUD_TYPE_NAMES[type] || type;
+ const typeColor = CLOUD_TYPE_COLORS[type] || CLOUD_TYPE_COLORS.others;
+
+ return (
+
+ );
+ })}
+
+
+ {/* 按网盘类型分类显示 */}
+ {filteredCloudTypes.map((cloudType) => {
+ const links = results.merged_by_type[cloudType];
+ if (!links || links.length === 0) return null;
+
+ const typeName = CLOUD_TYPE_NAMES[cloudType] || cloudType;
+ const typeColor = CLOUD_TYPE_COLORS[cloudType] || CLOUD_TYPE_COLORS.others;
+
+ return (
+
+ {/* 网盘类型标题 */}
+
+
+ {typeName}
+
+
+ {links.length} 个链接
+
+
+
+ {/* 链接列表 */}
+
+ {links.map((link: PansouLink, index: number) => (
+
+ {/* 资源标题 */}
+ {link.note && (
+
+ {link.note}
+
+ )}
+
+ {/* 链接和密码 */}
+
+
+
+ {link.url}
+
+ {link.password && (
+
+ 提取码: {link.password}
+
+ )}
+
+
+ {/* 操作按钮 */}
+
+
+
+
+
+
+ {/* 来源和时间 */}
+
+ {link.source && (
+ 来源: {link.source}
+ )}
+ {link.datetime && (
+ {new Date(link.datetime).toLocaleDateString()}
+ )}
+
+
+ {/* 图片预览 */}
+ {link.images && link.images.length > 0 && (
+
+ {link.images.map((img, imgIndex) => (
+

+ ))}
+
+ )}
+
+ ))}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts
index e76fa01..bd51ec2 100644
--- a/src/lib/admin.types.ts
+++ b/src/lib/admin.types.ts
@@ -22,6 +22,10 @@ export interface AdminConfig {
// TMDB配置
TMDBApiKey?: string;
TMDBProxy?: string;
+ // Pansou配置
+ PansouApiUrl?: string;
+ PansouUsername?: string;
+ PansouPassword?: string;
// 评论功能开关
EnableComments: boolean;
// 自定义去广告代码
diff --git a/src/lib/pansou.client.ts b/src/lib/pansou.client.ts
new file mode 100644
index 0000000..b928d3d
--- /dev/null
+++ b/src/lib/pansou.client.ts
@@ -0,0 +1,218 @@
+/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
+
+/**
+ * Pansou 网盘搜索 API 客户端
+ * 文档: https://github.com/fish2018/pansou
+ */
+
+// Token 缓存
+let cachedToken: string | null = null;
+let tokenExpiry: number | null = null;
+
+export interface PansouLink {
+ url: string;
+ password: string;
+ note: string;
+ datetime: string;
+ source: string;
+ images?: string[];
+}
+
+export interface PansouSearchResult {
+ total: number;
+ merged_by_type?: {
+ [key: string]: PansouLink[];
+ };
+}
+
+export interface PansouLoginResponse {
+ token: string;
+ expires_at: number;
+ username: string;
+}
+
+/**
+ * 登录 Pansou 获取 Token
+ */
+export async function loginPansou(
+ apiUrl: string,
+ username: string,
+ password: string
+): Promise {
+ try {
+ const response = await fetch(`${apiUrl}/api/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ username, password }),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || '登录失败');
+ }
+
+ const data: PansouLoginResponse = await response.json();
+
+ // 缓存 Token
+ cachedToken = data.token;
+ tokenExpiry = data.expires_at;
+
+ return data.token;
+ } catch (error) {
+ console.error('Pansou 登录失败:', error);
+ throw error;
+ }
+}
+
+/**
+ * 获取有效的 Token(自动处理登录和缓存)
+ */
+async function getValidToken(
+ apiUrl: string,
+ username?: string,
+ password?: string
+): Promise {
+ // 如果没有配置账号密码,返回 null(不需要认证)
+ if (!username || !password) {
+ return null;
+ }
+
+ // 检查缓存的 Token 是否有效
+ if (cachedToken && tokenExpiry) {
+ const now = Math.floor(Date.now() / 1000);
+ // 提前 5 分钟刷新 Token
+ if (tokenExpiry - now > 300) {
+ return cachedToken;
+ }
+ }
+
+ // Token 过期或不存在,重新登录
+ try {
+ return await loginPansou(apiUrl, username, password);
+ } catch (error) {
+ console.error('获取 Pansou Token 失败:', error);
+ return null;
+ }
+}
+
+/**
+ * 搜索网盘资源
+ */
+export async function searchPansou(
+ apiUrl: string,
+ keyword: string,
+ options?: {
+ username?: string;
+ password?: string;
+ refresh?: boolean;
+ cloudTypes?: string[];
+ }
+): Promise {
+ try {
+ // 获取 Token(如果需要认证)
+ const token = await getValidToken(
+ apiUrl,
+ options?.username,
+ options?.password
+ );
+
+ // 构建请求头
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // 构建请求体
+ const body: any = {
+ kw: keyword,
+ res: 'merge', // 只返回按网盘类型分类的结果
+ };
+
+ if (options?.refresh) {
+ body.refresh = true;
+ }
+
+ if (options?.cloudTypes && options.cloudTypes.length > 0) {
+ body.cloud_types = options.cloudTypes;
+ }
+
+ const response = await fetch(`${apiUrl}/api/search`, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || error.message || '搜索失败');
+ }
+
+ const responseData = await response.json();
+
+ // Pansou API 返回的数据结构是 { code, message, data }
+ // 实际数据在 data 字段中
+ let data: PansouSearchResult;
+
+ if (responseData.data) {
+ // 如果有 data 字段,使用 data 中的内容
+ data = responseData.data;
+ } else {
+ // 否则直接使用返回的数据
+ data = responseData;
+ }
+
+ // 验证返回的数据结构
+ if (!data || typeof data !== 'object') {
+ throw new Error('返回数据格式错误');
+ }
+
+ // 确保 merged_by_type 存在
+ if (!data.merged_by_type) {
+ data.merged_by_type = {};
+ }
+
+ // 确保 total 存在
+ if (typeof data.total !== 'number') {
+ data.total = 0;
+ }
+
+ return data;
+ } catch (error) {
+ console.error('Pansou 搜索失败:', error);
+ throw error;
+ }
+}
+
+/**
+ * 清除缓存的 Token
+ */
+export function clearPansouToken(): void {
+ cachedToken = null;
+ tokenExpiry = null;
+}
+
+/**
+ * 检查 Pansou 服务是否可用
+ */
+export async function checkPansouHealth(apiUrl: string): Promise {
+ try {
+ const response = await fetch(`${apiUrl}/api/health`, {
+ method: 'GET',
+ });
+
+ if (!response.ok) {
+ return false;
+ }
+
+ const data = await response.json();
+ return data.status === 'ok';
+ } catch (error) {
+ console.error('Pansou 健康检查失败:', error);
+ return false;
+ }
+}