Initial commit: GovAI 政务AI平台
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import api from "@/lib/api";
|
||||
import type { App, Category } from "@/lib/types";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { AppCard } from "@/components/app-card/app-card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Sparkles, LayoutGrid } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
function SectionHeader({
|
||||
title,
|
||||
icon: Icon,
|
||||
href,
|
||||
}: {
|
||||
title: string;
|
||||
icon?: LucideIcon;
|
||||
href?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-lg font-bold flex items-center gap-2">
|
||||
<span className="inline-block w-1 h-5 bg-blue-800 rounded-full mr-1" />
|
||||
{Icon && <Icon className="h-5 w-5 text-blue-700" />}
|
||||
{title}
|
||||
</h2>
|
||||
{href && (
|
||||
<Link
|
||||
href={href}
|
||||
className="text-sm text-blue-700 hover:text-blue-900 font-medium transition-colors"
|
||||
>
|
||||
查看全部 →
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AppGridSkeleton({ count = 4 }: { count?: number }) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-36 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StorePage() {
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.get("q") || "";
|
||||
const { user } = useAuthStore();
|
||||
const orgId = user?.org_id || "";
|
||||
const orgParam = orgId ? `org_id=${orgId}` : "";
|
||||
|
||||
const { data: categories } = useQuery({
|
||||
queryKey: ["categories", orgId],
|
||||
queryFn: () => api.get<Category[]>(`/api/v1/store/categories?${orgParam}`),
|
||||
});
|
||||
|
||||
const { data: featured, isLoading: featuredLoading } = useQuery({
|
||||
queryKey: ["featured", orgId],
|
||||
queryFn: () => api.get<App[]>(`/api/v1/store/featured?${orgParam}`),
|
||||
enabled: !query,
|
||||
});
|
||||
|
||||
const { data: topApps, isLoading: topLoading } = useQuery({
|
||||
queryKey: ["topApps", orgId],
|
||||
queryFn: () => api.get<App[]>(`/api/v1/store/rankings?${orgParam}`),
|
||||
enabled: !query,
|
||||
});
|
||||
|
||||
const { data: searchResults, isLoading: searchLoading } = useQuery({
|
||||
queryKey: ["search", query, orgId],
|
||||
queryFn: () => api.get<{ items: App[] }>(`/api/v1/store/apps?q=${encodeURIComponent(query)}&${orgParam}`),
|
||||
enabled: !!query,
|
||||
});
|
||||
|
||||
if (query) {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-7xl px-3 md:px-6 lg:px-8 py-4 md:py-6">
|
||||
<h1 className="text-xl font-bold mb-4">
|
||||
搜索结果:“{query}”
|
||||
</h1>
|
||||
{searchLoading ? (
|
||||
<AppGridSkeleton count={8} />
|
||||
) : searchResults?.items?.length ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{searchResults.items.map((app) => (
|
||||
<AppCard key={app.id} app={app} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
未找到相关政务应用
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-7xl px-3 md:px-6 lg:px-8 py-4 md:py-8 space-y-6 md:space-y-10">
|
||||
{/* Featured */}
|
||||
<section>
|
||||
<SectionHeader title="推荐应用" icon={Sparkles} />
|
||||
{featuredLoading ? (
|
||||
<AppGridSkeleton />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{featured?.map((app) => (
|
||||
<AppCard key={app.id} app={app} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Categories */}
|
||||
<section>
|
||||
<SectionHeader title="应用分类" icon={LayoutGrid} />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Link href="/store">
|
||||
<Badge variant="default" className="cursor-pointer">全部</Badge>
|
||||
</Link>
|
||||
{categories?.map((cat) => (
|
||||
<Link key={cat.id} href={`/store/category/${cat.slug}`}>
|
||||
<Badge variant="secondary" className="cursor-pointer hover:bg-secondary/80">
|
||||
{cat.name}
|
||||
{cat.app_count != null && cat.app_count > 0 && (
|
||||
<span className="ml-1 text-muted-foreground">{cat.app_count}</span>
|
||||
)}
|
||||
</Badge>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* All Apps */}
|
||||
<section>
|
||||
<SectionHeader title="全部应用" icon={LayoutGrid} />
|
||||
{topLoading ? (
|
||||
<AppGridSkeleton />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{topApps?.map((app) => (
|
||||
<AppCard key={app.id} app={app} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user