#!/usr/bin/env node /** * Frontend Builder Agent — SF-03 * * 根据 PRD (SF-01) + Architecture (SF-02) 自动生成前端项目。 * * 技术栈:Next.js 15 App Router + TypeScript + Tailwind CSS * * 生成内容: * - 页面组件(非 stub,包含真实 JSX/state/hooks) * - Root Layout + 导航 * - 共享组件(Card, Button, Input, Modal, 领域组件) * - Custom Hooks(数据获取/表单/状态) * - API Services(类型安全的 API 调用层) * - TypeScript 类型定义 * - 项目配置(package.json, tsconfig, tailwind, next.config) * * Usage: * node scripts/frontend-builder-agent.mjs --prd --arch [options] * node scripts/frontend-builder-agent.mjs --input-text "<需求>" [options] * * Options: * --prd PRD JSON(SF-01 输出) * --arch Architecture JSON(SF-02 输出) * --input-text 直接传入需求(自动调 SF-01 → SF-02 → SF-03) * --output 输出目录(default: frontend/) * --verbose 详细输出 * --help 显示帮助 * * @module frontend-builder-agent */ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { createContract, toCamel, tsType as contractTsType, createInputFields } from "./model-contract.mjs"; const __dirname = dirname(resolve(fileURLToPath(import.meta.url))); const WORKSPACE = resolve(__dirname, ".."); const DEFAULT_OUTPUT = resolve(WORKSPACE, "frontend"); // ═══════════════════════════════════════════════════════ // 1. TypeScript Type Generator // ═══════════════════════════════════════════════════════ /** * Map domain to TypeScript interfaces. */ function generateTypes(prd, arch, contract) { const domain = prd.domain || "generic"; const interfaces = [`// Auto-generated types for ${prd.projectName}`]; interfaces.push("\n// ─── Base ───────────────────────────────────────────\n"); // Base types interfaces.push(` // ─── Base ─────────────────────────────────────────── export interface User { id: string; phone: string; nickname: string; avatarUrl: string; createdAt: string; updatedAt: string; }`); // ─── Entity types from Contract ─── for (const entity of contract.entities.filter(e => e.table !== "users")) { const name = entity.name; interfaces.push(`export interface ${name} {`); for (const f of entity.fields) { const fname = toCamel(contract, f.name); const ft = contractTsType(f); const optional = f.required ? "" : "?"; interfaces.push(` ${fname}${optional}: ${ft};`); } interfaces.push("}\n"); } // Domain-specific extra types (legacy — for pages that need them) const domainTypes = { pet: ` export interface Pet { id: string; ownerId: string; name: string; species: string; breed: string; birthDate: string; weightKg: number; avatarUrl: string; createdAt: string; } export interface Schedule { id: string; petId: string; type: "vaccine" | "deworming" | "checkup" | "grooming"; title: string; scheduledAt: string; completed: boolean; } export interface DailyLog { id: string; petId: string; category: "food" | "exercise" | "excretion" | "weight"; value: string; loggedAt: string; } export interface Hospital { id: string; name: string; address: string; phone: string; rating: number; distance: number; services: string[]; }`, ecommerce: ` export interface Product { id: string; title: string; price: number; stock: number; images: string[]; categoryId: string; createdAt: string; } export interface Order { id: string; userId: string; status: "pending" | "paid" | "shipped" | "delivered" | "cancelled"; totalAmount: number; addressId: string; createdAt: string; } export interface Logistics { id: string; orderId: string; carrier: string; trackingNumber: string; status: string; events: LogisticsEvent[]; } export interface LogisticsEvent { timestamp: string; location: string; description: string; }`, education: ` export interface Course { id: string; title: string; instructorId: string; price: number; coverUrl: string; createdAt: string; } export interface Lesson { id: string; courseId: string; title: string; videoUrl: string; durationSec: number; sortOrder: number; } export interface Exercise { id: string; lessonId: string; question: string; options: string[]; answer: string; } export interface Progress { id: string; userId: string; lessonId: string; completed: boolean; watchedSec: number; }`, enterprise: ` export interface Department { id: string; name: string; parentId: string | null; managerId: string; } export interface Approval { id: string; applicantId: string; type: "leave" | "expense" | "overtime" | "travel"; status: "pending" | "approved" | "rejected"; formData: Record; createdAt: string; } export interface Attendance { id: string; userId: string; checkInAt: string; checkOutAt: string | null; location: { lat: number; lng: number; address: string }; type: "normal" | "remote" | "overtime"; }`, fitness: ` export interface CheckIn { id: string; userId: string; type: string; durationMin: number; calories: number; note: string; checkedAt: string; } export interface TrainingPlan { id: string; userId: string; title: string; schedule: Record; active: boolean; }`, note: ` export interface Note { id: string; userId: string; title: string; content: string; isMarkdown: boolean; folderId: string | null; createdAt: string; updatedAt: string; } export interface Tag { id: string; name: string; color: string; }`, }; // (Entity types now generated from contract above) // API response types interfaces.push(` // ─── API Responses ────────────────────────────────── export interface ApiResponse { data: T; message: string; } export interface PaginatedResponse extends ApiResponse { total: number; page: number; pageSize: number; } export interface ApiError { code: string; message: string; details?: Record; }`); return interfaces.join("\n"); } // ═══════════════════════════════════════════════════════ // 2. API Service Generator // ═══════════════════════════════════════════════════════ function generateApiServices(prd, arch) { const apiDesign = arch.apiDesign || []; const files = {}; // Base API client files["services/api.ts"] = `// Auto-generated API client for ${prd.projectName} const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api"; interface RequestOptions extends RequestInit { params?: Record; } async function request(endpoint: string, options: RequestOptions = {}): Promise { const { params, ...init } = options; let url = \`\${API_BASE}\${endpoint}\`; if (params) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([k, v]) => searchParams.set(k, String(v))); url += \`?\${searchParams.toString()}\`; } const res = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...init.headers, }, }); if (!res.ok) { const err = await res.json().catch(() => ({ message: res.statusText })); throw new Error(err.message || \`API Error: \${res.status}\`); } return res.json(); } export const api = { get: (url: string, params?: Record) => request(url, { method: "GET", params }), post: (url: string, data?: unknown) => request(url, { method: "POST", body: JSON.stringify(data) }), put: (url: string, data?: unknown) => request(url, { method: "PUT", body: JSON.stringify(data) }), delete: (url: string) => request(url, { method: "DELETE" }), }; `; // Resource-specific services for (const group of apiDesign) { const resource = group.resource; const endpoints = group.endpoints; // Build service methods const methods = []; for (const ep of endpoints) { const fnName = ep.method.toLowerCase() + ep.path .replace(`/api/${resource}`, "") .replace(/[\/:]/g, "_") .replace(/^_/, "") || "list"; let fnBody; let fnParams = ""; if (ep.method === "GET") { if (ep.path.includes(":id")) { fnParams = "id: string"; fnBody = `api.get(\`/api/${resource}/\${id}\`)`; } else { fnBody = `api.get("/api/${resource}")`; } } else if (ep.method === "POST") { fnBody = `api.post("/api/${resource}", data)`; fnParams = `data: Record`; } else if (ep.method === "PUT") { fnParams = `id: string, data: Record`; fnBody = `api.put(\`/api/${resource}/\${id}\`, data)`; } else if (ep.method === "DELETE") { fnParams = "id: string"; fnBody = `api.delete(\`/api/${resource}/\${id}\`)`; } // Clean up function name const cleanName = fnName.replace(/_{2,}/g, "_").replace(/_$/, "") || "list"; methods.push(`export function ${cleanName}(${fnParams}) {\n return ${fnBody};\n}`); } files[`services/${resource}.ts`] = `// Auto-generated ${resource} service import { api } from "./api"; ${methods.join("\n\n")} `; } return files; } function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); } // ═══════════════════════════════════════════════════════ // 3. Custom Hooks Generator // ═══════════════════════════════════════════════════════ function generateHooks(prd, arch) { const apiDesign = arch.apiDesign || []; const files = {}; for (const group of apiDesign) { const resource = group.resource; // Sanitize: "daily-logs" → "DailyLogs", remove any non-alphanumeric except underscore const sanitized = capitalize(resource).replace(/[^a-zA-Z0-9_]/g, ""); const name = sanitized || "Item"; // Singularize type name: Albums → Album, Pets → Pet const singularName = name.replace(/ies$/, "y").replace(/ses$/, "s").replace(/xes$/, "x").replace(/ches$/, "ch").replace(/shes$/, "sh").replace(/s$/i, ""); const typeNameForHook = singularName || name; files[`hooks/use${name}.ts`] = `"use client"; import { useState, useEffect, useCallback } from "react"; // Generic data type for ${resource} type ${typeNameForHook} = Record; /** * Fetch and manage ${resource} data. */ export function use${name}() { const [data, setData] = useState<${typeNameForHook}[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setError(null); const res = await fetch(\`/api/${resource}\`); const json = await res.json(); setData(Array.isArray(json) ? json : json?.data || []); } catch (e) { setError(e instanceof Error ? e.message : "Failed to load data"); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } `; } // Generic hooks files["hooks/useForm.ts"] = `"use client"; import { useState, useCallback } from "react"; interface UseFormOptions { initialValues: T; onSubmit: (values: T) => Promise; } export function useForm>({ initialValues, onSubmit }: UseFormOptions) { const [values, setValues] = useState(initialValues); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const setField = useCallback((field: K, value: T[K]) => { setValues(prev => ({ ...prev, [field]: value })); }, []); const handleSubmit = useCallback(async (e: React.FormEvent) => { e.preventDefault(); try { setSubmitting(true); setError(null); await onSubmit(values); } catch (err) { setError(err instanceof Error ? err.message : "Submit failed"); } finally { setSubmitting(false); } }, [values, onSubmit]); return { values, setField, submitting, error, handleSubmit }; } `; files["hooks/useDebounce.ts"] = `"use client"; import { useState, useEffect } from "react"; export function useDebounce(value: T, delay: number = 300): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } `; return files; } // ═══════════════════════════════════════════════════════ // 4. Component Generator // ═══════════════════════════════════════════════════════ function generateComponents(prd, arch) { const files = {}; const domain = prd.domain || "generic"; const projectName = prd.projectName || "App"; // ── Shared UI Components ────────────────────── files["components/ui/Button.tsx"] = `"use client"; import { type ButtonHTMLAttributes } from "react"; interface ButtonProps extends ButtonHTMLAttributes { variant?: "primary" | "secondary" | "danger" | "ghost"; size?: "sm" | "md" | "lg"; loading?: boolean; } export function Button({ variant = "primary", size = "md", loading, children, className = "", disabled, ...props }: ButtonProps) { const base = "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; const variants = { primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500", secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200 focus:ring-gray-400", danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500", ghost: "text-gray-600 hover:bg-gray-100 focus:ring-gray-400", }; const sizes = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2 text-sm", lg: "px-6 py-3 text-base", }; return ( ); } `; files["components/ui/Card.tsx"] = `import type { ReactNode } from "react"; interface CardProps { children: ReactNode; className?: string; onClick?: () => void; } export function Card({ children, className = "", onClick }: CardProps) { return (
{children}
); } `; files["components/ui/Input.tsx"] = `"use client"; import { type InputHTMLAttributes } from "react"; interface InputProps extends InputHTMLAttributes { label?: string; error?: string; } export function Input({ label, error, className = "", id, ...props }: InputProps) { return (
{label && } {error &&

{error}

}
); } `; files["components/ui/Modal.tsx"] = `"use client"; import { useEffect, type ReactNode } from "react"; interface ModalProps { open: boolean; onClose: () => void; title: string; children: ReactNode; } export function Modal({ open, onClose, title, children }: ModalProps) { useEffect(() => { if (open) document.body.style.overflow = "hidden"; else document.body.style.overflow = ""; return () => { document.body.style.overflow = ""; }; }, [open]); if (!open) return null; return (

{title}

{children}
); } `; files["components/ui/EmptyState.tsx"] = `import type { ReactNode } from "react"; interface EmptyStateProps { icon?: string; title: string; description?: string; action?: ReactNode; } export function EmptyState({ icon = "📭", title, description, action }: EmptyStateProps) { return (
{icon}

{title}

{description &&

{description}

} {action}
); } `; // ── Domain-specific components ────────────────── if (domain === "pet") { files["components/pet/PetCard.tsx"] = `import type { Pet } from "@/types"; import { Card } from "@/components/ui/Card"; interface PetCardProps { pet: Pet; onClick?: () => void; } export function PetCard({ pet, onClick }: PetCardProps) { return (
{pet.avatarUrl ? ( {pet.name} ) : ( "🐾" )}

{pet.name}

{pet.breed || pet.species} · {pet.weightKg}kg

); } `; files["components/pet/ScheduleCard.tsx"] = `import type { Schedule } from "@/types"; interface ScheduleCardProps { schedule: Schedule; onToggle?: () => void; } export function ScheduleCard({ schedule, onToggle }: ScheduleCardProps) { const typeIcons: Record = { vaccine: "💉", deworming: "💊", checkup: "🩺", grooming: "✂️", }; const date = new Date(schedule.scheduledAt); const isOverdue = date < new Date() && !schedule.completed; return (
{typeIcons[schedule.type] || "📅"}

{schedule.title}

{date.toLocaleDateString("zh-CN", { month: "short", day: "numeric" })}

); } `; } if (domain === "ecommerce") { files["components/ecommerce/ProductCard.tsx"] = `import type { Product } from "@/types"; import { Card } from "@/components/ui/Card"; interface ProductCardProps { product: Product; onClick?: () => void; } export function ProductCard({ product, onClick }: ProductCardProps) { return (
{product.images?.[0] ? {product.title} : "📦"}

{product.title}

¥{product.price} 库存 {product.stock}
); } `; } if (domain === "note") { files["components/note/NoteCard.tsx"] = `import type { Note } from "@/types"; import { Card } from "@/components/ui/Card"; interface NoteCardProps { note: Note; onClick?: () => void; } export function NoteCard({ note, onClick }: NoteCardProps) { return (

{note.title || "无标题"}

{note.content?.slice(0, 120) || "空笔记"}

{note.isMarkdown && MD} {new Date(note.updatedAt).toLocaleDateString("zh-CN")}
); } `; } return files; } // ═══════════════════════════════════════════════════════ // 5. Page Generator // ═══════════════════════════════════════════════════════ function generatePages(prd, arch) { const pages = prd.pages || []; const apiDesign = arch.apiDesign || []; const domain = prd.domain || "generic"; const projectName = prd.projectName || "App"; const files = {}; // ── Home Page ────────────────────────────────── const homePage = pages.find(p => p.route === "/home" || p.name === "首页" || p.name === "主页"); if (homePage || pages.length > 0) { const features = prd.features?.slice(0, 4).map(f => f.name) || []; files["app/page.tsx"] = generateHomePage(projectName, domain, homePage || pages[0], features); } // ── Dynamic pages ────────────────────────────── for (const page of pages) { const route = page.route; if (!route || route === "/home") continue; // Clean route: /pet/:id → pet/[id], /daily-log → daily-log const dirPath = route .replace(/^\//, "") .replace(/:(\w+)/g, "[$1]") .replace(/\/$/, ""); const pageContent = generatePageComponent(page, domain, apiDesign, prd.features); files[`app/${dirPath}/page.tsx`] = pageContent; } return files; } /** * Generate a full, meaningful home page component. */ function generateHomePage(projectName, domain, homePage, features) { const name = homePage?.name || "首页"; const desc = homePage?.description || `${projectName} 首页`; return `import Link from "next/link"; export default function HomePage() { return (
{/* Hero Section */}

${projectName}

${desc}

{/* Quick Actions */}

快捷入口

${features.map((f, i) => { const icons = ["📋", "📅", "📝", "📸", "🏥", "📚", "💬"]; return ` ${icons[i] || "📌"} ${f} `; }).join("\n ")}
{/* Recent Activity / Stats */}

今日概览

欢迎使用 ${projectName}

开始探索功能吧

👋
); } `; } /** * Generate a real page component (not a stub). */ function generatePageComponent(page, domain, apiDesign, features) { const name = page.name; const route = page.route; const desc = page.description; // Resource name from route: /pet/:id → pets, /schedule → schedules const resourceSeg = route.replace(/^\//, "").split("/")[0].replace(/:.*/, ""); // Find matching API group const apiGroup = apiDesign.find(g => g.resource === resourceSeg || g.resource === resourceSeg + "s" || g.resource + "s" === resourceSeg ); const hookNameRaw = apiGroup ? capitalize(apiGroup.resource) : null; // Singularize for type references: albums → Album, pets → Pet const rawTypeName = apiGroup ? capitalize(apiGroup.resource) : null; const typeName = rawTypeName ? rawTypeName.replace(/ies$/, "y").replace(/ses$/, "s").replace(/xes$/, "x").replace(/ches$/, "ch").replace(/shes$/, "sh").replace(/s$/i, "") : null; // Generate realistic page content switch (domain) { case "pet": if (route.includes("pet")) return generatePetDetailPage(name, desc); if (route.includes("schedule")) return generateSchedulePage(name, desc); if (route.includes("daily-log")) return generateDailyLogPage(name, desc); if (route.includes("album")) return generateAlbumPage(name, desc); if (route.includes("hospital")) return generateHospitalPage(name, desc); if (route.includes("profile")) return generateProfilePage(name, desc, "pet"); break; case "ecommerce": if (route.includes("product")) return generateProductDetailPage(name, desc); if (route.includes("cart")) return generateCartPage(name, desc); if (route.includes("order")) return generateOrderPage(name, desc); if (route.includes("profile")) return generateProfilePage(name, desc, "ecommerce"); break; case "note": if (route.includes("note")) return generateNoteEditorPage(name, desc); if (route.includes("tag")) return generateTagPage(name, desc); if (route.includes("settings")) return generateSettingsPage(name, desc); break; case "enterprise": if (route.includes("attendance")) return generateAttendancePage(name, desc); if (route.includes("approval")) return generateApprovalPage(name, desc); if (route.includes("contact")) return generateContactPage(name, desc); break; case "education": if (route.includes("course")) return generateCoursePage(name, desc); if (route.includes("learn")) return generateLearnPage(name, desc); if (route.includes("exercise")) return generateExercisePage(name, desc); if (route.includes("live")) return generateLivePage(name, desc); break; case "fitness": if (route.includes("checkin")) return generateCheckinPage(name, desc); if (route.includes("stat")) return generateStatsPage(name, desc); break; } // Generic fallback page return `"use client"; import { useState } from "react"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { EmptyState } from "@/components/ui/EmptyState"; ${hookNameRaw ? `import { use${hookNameRaw} } from "@/hooks/use${hookNameRaw}";` : ""} export default function ${sanitizeComponentName(name)}Page() { ${hookNameRaw ? `const { data, loading, error, refetch } = use${hookNameRaw}();` : ""} const [open, setOpen] = useState(false); return (

${name}

${desc}

{${hookNameRaw ? `loading ? (
) : error ? (

{error}

) : data.length === 0 ? ( setOpen(true)} variant="primary" size="sm">创建第一条} /> ) : (
{data.map((item: any) => (

{item.title || item.name || \`Item \${item.id.slice(0, 8)}\`}

{item.createdAt || ""}

))}
)}` : `

${name} 页面内容

路由: ${route}

}`}
); } `; } // ── Domain-specific page generators ────────────── function generatePetDetailPage(name, desc) { return `"use client"; import { useState, useEffect } from "react"; import { useParams } from "next/navigation"; import type { Pet, Schedule } from "@/types"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { PetCard } from "@/components/pet/PetCard"; import { ScheduleCard } from "@/components/pet/ScheduleCard"; import { EmptyState } from "@/components/ui/EmptyState"; export default function PetDetailPage() { const params = useParams(); const [pet, setPet] = useState(null); const [schedules, setSchedules] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { // TODO: Replace with actual API call const loadData = async () => { setLoading(true); // Simulated data setPet({ id: params.id as string, ownerId: "u1", name: "毛球", species: "猫", breed: "英短", birthDate: "2023-03-15", weightKg: 4.5, avatarUrl: "", createdAt: new Date().toISOString(), }); setSchedules([ { id: "s1", petId: params.id as string, type: "vaccine", title: "年度疫苗", scheduledAt: "2026-07-01T09:00:00Z", completed: false }, { id: "s2", petId: params.id as string, type: "deworming", title: "季度驱虫", scheduledAt: "2026-06-15T10:00:00Z", completed: false }, { id: "s3", petId: params.id as string, type: "checkup", title: "体检", scheduledAt: "2026-05-20T14:00:00Z", completed: true }, ]); setLoading(false); }; loadData(); }, [params.id]); if (loading) return
; if (!pet) return ; return (
{/* Pet Profile */}
{pet.avatarUrl ? {pet.name} : "🐱"}

{pet.name}

{pet.breed} · {pet.weightKg}kg · {pet.species}

{/* Health Schedule */}

健康日程

{schedules.length > 0 ? schedules.map(s => ) : }
{/* Quick Info */}

{pet.weightKg}

体重 (kg)

{new Date().getFullYear() - new Date(pet.birthDate).getFullYear()}岁

年龄

); } `; } function generateSchedulePage(name, desc) { return `"use client"; import { useState } from "react"; import type { Schedule } from "@/types"; import { Button } from "@/components/ui/Button"; import { ScheduleCard } from "@/components/pet/ScheduleCard"; import { Modal } from "@/components/ui/Modal"; import { Input } from "@/components/ui/Input"; import { EmptyState } from "@/components/ui/EmptyState"; const mockSchedules: Schedule[] = [ { id: "s1", petId: "p1", type: "vaccine", title: "年度疫苗加强针", scheduledAt: "2026-07-01T09:00:00Z", completed: false }, { id: "s2", petId: "p1", type: "deworming", title: "季度驱虫", scheduledAt: "2026-06-15T10:00:00Z", completed: false }, { id: "s3", petId: "p1", type: "checkup", title: "半年度体检", scheduledAt: "2026-05-20T14:00:00Z", completed: true }, { id: "s4", petId: "p1", type: "grooming", title: "美容护理", scheduledAt: "2026-06-10T11:00:00Z", completed: false }, ]; export default function SchedulePage() { const [schedules, setSchedules] = useState(mockSchedules); const [showModal, setShowModal] = useState(false); const toggleComplete = (id: string) => { setSchedules(prev => prev.map(s => s.id === id ? { ...s, completed: !s.completed } : s)); }; const pending = schedules.filter(s => !s.completed); const done = schedules.filter(s => s.completed); return (

健康日程

管理宠物的疫苗、驱虫、体检安排

{/* Pending */}

待完成 ({pending.length})

{pending.length > 0 ? pending.map(s => toggleComplete(s.id)} />) : }
{/* Completed */} {done.length > 0 && (

已完成 ({done.length})

{done.map(s => )}
)} setShowModal(false)} title="添加日程">
); } `; } function generateDailyLogPage(name, desc) { return `"use client"; import { useState } from "react"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { EmptyState } from "@/components/ui/EmptyState"; const CATEGORIES = [ { key: "food", icon: "🍖", label: "饮食" }, { key: "exercise", icon: "🏃", label: "运动" }, { key: "excretion", icon: "💩", label: "排泄" }, { key: "weight", icon: "⚖️", label: "体重" }, ]; interface LogEntry { id: string; category: string; icon: string; value: string; time: string; } export default function DailyLogPage() { const [logs, setLogs] = useState([]); const [category, setCategory] = useState("food"); const [value, setValue] = useState(""); const addLog = () => { if (!value.trim()) return; const cat = CATEGORIES.find(c => c.key === category)!; setLogs(prev => [{ id: Date.now().toString(), category: cat.key, icon: cat.icon, value, time: new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }), }, ...prev]); setValue(""); }; return (

日常记录

记录宠物的饮食、运动、排泄和体重变化

{/* Quick Record */}
{CATEGORIES.map(c => ( ))}
setValue(e.target.value)} onKeyDown={e => e.key === "Enter" && addLog()} />
{/* Log Timeline */}
{logs.length > 0 ? logs.map(log => (
{log.icon} {log.value} {log.time}
)) : }
); } `; } function generateAlbumPage(name, desc) { return `"use client"; import { useState } from "react"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { EmptyState } from "@/components/ui/EmptyState"; const mockPhotos = Array.from({ length: 8 }, (_, i) => ({ id: \`p\${i}\`, color: ["#FEE2E2", "#FEF3C7", "#D1FAE5", "#DBEAFE", "#EDE9FE", "#FCE7F3", "#E0E7FF", "#FFEDD5"][i], emoji: ["🐱", "🐶", "🐱", "🐶", "🐱", "🐱", "🐶", "🐱"][i], date: new Date(2026, 5, i + 1).toISOString(), })); export default function AlbumPage() { const [photos] = useState(mockPhotos); return (

成长相册

记录宠物的成长瞬间

{photos.map(photo => (
{photo.emoji}
))}

下拉加载更多照片

); } `; } function generateHospitalPage(name, desc) { return `"use client"; import { useState } from "react"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; const mockHospitals = [ { id: "h1", name: "宠颐生动物医院", address: "朝阳区建国路88号", phone: "010-88886666", rating: 4.8, distance: 1.2, services: ["疫苗", "体检", "手术", "美容"] }, { id: "h2", name: "瑞鹏宠物医院", address: "海淀区中关村大街1号", phone: "010-66668888", rating: 4.6, distance: 2.8, services: ["急诊", "住院", "化验"] }, { id: "h3", name: "佳雯宠物诊所", address: "西城区金融街16号", phone: "010-55556666", rating: 4.5, distance: 3.5, services: ["疫苗", "驱虫", "体检"] }, ]; export default function HospitalPage() { const [search, setSearch] = useState(""); const filtered = mockHospitals.filter(h => h.name.includes(search) || h.address.includes(search) ); return (

附近医院

查找附近的宠物医院

setSearch(e.target.value)} />
{filtered.map(h => (
🏥

{h.name}

⭐ {h.rating}

{h.address}

{h.distance}km · {h.phone}

{h.services.map(s => {s})}
))}
); } `; } function generateProfilePage(name, desc, domain) { return `"use client"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; const menuItems = [ { icon: "⚙️", label: "设置", href: "#" }, { icon: "🔔", label: "通知", href: "#" }, { icon: "❓", label: "帮助与反馈", href: "#" }, { icon: "📄", label: "关于我们", href: "#" }, ]; export default function ProfilePage() { return (
{/* User Info */}
😊

用户昵称

138****1234

{/* Stats */}
${domain === "pet" ? `

3

我的宠物

12

健康日程

56

日常记录

` : `

0

收藏

0

订单

0

足迹

`}
{/* Menu */} {menuItems.map((item, i) => ( {item.icon} {item.label} ))}
); } `; } // More domain-specific page generators... (abbreviated for space) function generateProductDetailPage(name, desc) { return `"use client"; import { Button } from "@/components/ui/Button"; export default function ProductDetailPage() { return (
📦

商品名称

¥99.00

库存充足 · 24小时内发货

商品详情

商品详细描述信息。这里会展示商品的全部规格参数、使用方法、注意事项等内容。

); }`; } function generateCartPage(name, desc) { return `"use client"; import { Card } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; export default function CartPage() { return (

购物车

购物车功能开发中...

); }`; } function generateOrderPage(name, desc) { return `"use client"; import { Card } from "@/components/ui/Card"; import { EmptyState } from "@/components/ui/EmptyState"; export default function OrderPage() { return (

我的订单

); }`; } function generateNoteEditorPage(name, desc) { return `"use client"; import { useState } from "react"; import { Input } from "@/components/ui/Input"; import { Button } from "@/components/ui/Button"; export default function NoteEditorPage() { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); return (

编辑笔记

setTitle(e.target.value)} placeholder="笔记标题" className="text-lg font-medium border-0 px-0" />