"use client" import { useState, useEffect, useRef } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Checkbox } from "@/components/ui/checkbox" import { Progress } from "@/components/ui/progress" import { Badge } from "@/components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { ScrollArea } from "@/components/ui/scroll-area" import { toast } from "@/components/ui/use-toast" import { Camera, Download, FileText, ImageIcon, Settings, Eye, Trash2, RefreshCw, Monitor, Smartphone, Tablet, Globe, CheckCircle, XCircle, Clock, Zap, } from "lucide-react" import { enhancedScreenshotService, type ScreenshotOptions } from "@/lib/documentation/enhanced-screenshot-service" import { generateDocx } from "@/lib/documentation/docx-generator" import { pageRegistry } from "@/lib/documentation/page-registry" interface Screenshot { id: string name: string url: string dataUrl?: string timestamp: Date status: "pending" | "success" | "failed" | "capturing" error?: string method?: string size?: { width: number; height: number } } interface DocumentSettings { title: string author: string description: string includeTimestamp: boolean includePageUrls: boolean imageFormat: "png" | "jpeg" | "webp" imageQuality: number pageSize: "A4" | "A3" | "Letter" orientation: "portrait" | "landscape" } export default function DocumentationPage() { const [screenshots, setScreenshots] = useState([]) const [selectedPages, setSelectedPages] = useState([]) const [isCapturing, setIsCapturing] = useState(false) const [captureProgress, setCaptureProgress] = useState(0) const [isGeneratingDoc, setIsGeneratingDoc] = useState(false) const [previewMode, setPreviewMode] = useState<"desktop" | "tablet" | "mobile">("desktop") const [currentPreviewUrl, setCurrentPreviewUrl] = useState("") const [documentSettings, setDocumentSettings] = useState({ title: "用户数据资产中台文档", author: "系统管理员", description: "自动生成的系统文档", includeTimestamp: true, includePageUrls: true, imageFormat: "png", imageQuality: 0.9, pageSize: "A4", orientation: "portrait", }) const previewRef = useRef(null) const [screenshotOptions, setScreenshotOptions] = useState({ format: "png", quality: 0.9, scale: 1, backgroundColor: "#ffffff", timeout: 30000, }) const pages = pageRegistry.getAllPages() useEffect(() => { // 初始化选中所有页面 setSelectedPages(pages.map((p) => p.path)) }, []) const handlePageSelection = (pagePath: string, checked: boolean) => { if (checked) { setSelectedPages((prev) => [...prev, pagePath]) } else { setSelectedPages((prev) => prev.filter((p) => p !== pagePath)) } } const selectAllPages = () => { setSelectedPages(pages.map((p) => p.path)) } const deselectAllPages = () => { setSelectedPages([]) } const getViewportSize = () => { switch (previewMode) { case "mobile": return { width: 375, height: 667 } case "tablet": return { width: 768, height: 1024 } default: return { width: 1920, height: 1080 } } } const captureScreenshot = async (page: (typeof pages)[0]): Promise => { const screenshot: Screenshot = { id: `${page.path}-${Date.now()}`, name: page.name, url: page.path, timestamp: new Date(), status: "capturing", } try { // 方法1: 尝试直接截图当前页面(如果是当前页面) if (window.location.pathname === page.path) { const result = await enhancedScreenshotService.captureViewport({ ...screenshotOptions, ...getViewportSize(), }) if (result.success && result.dataUrl) { return { ...screenshot, status: "success", dataUrl: result.dataUrl, method: result.method, size: getViewportSize(), } } } // 方法2: 使用新窗口截图 const newWindow = window.open( page.path, "_blank", `width=${getViewportSize().width},height=${getViewportSize().height}`, ) if (!newWindow) { throw new Error("无法打开新窗口,请检查浏览器弹窗设置") } // 等待页面加载 await new Promise((resolve, reject) => { const timeout = setTimeout(() => { newWindow.close() reject(new Error("页面加载超时")) }, 15000) const checkLoaded = () => { try { if (newWindow.document && newWindow.document.readyState === "complete") { clearTimeout(timeout) resolve(void 0) } else { setTimeout(checkLoaded, 100) } } catch (e) { // 跨域问题,等待一段时间后继续 setTimeout(checkLoaded, 100) } } newWindow.addEventListener("load", () => { clearTimeout(timeout) setTimeout(resolve, 2000) // 额外等待2秒确保渲染完成 }) checkLoaded() }) // 尝试截图新窗口 let result try { if (newWindow.document && newWindow.document.body) { result = await enhancedScreenshotService.captureElement(newWindow.document.body, { ...screenshotOptions, ...getViewportSize(), }) } } catch (e) { console.warn("新窗口截图失败,尝试其他方法:", e) } newWindow.close() if (result?.success && result.dataUrl) { return { ...screenshot, status: "success", dataUrl: result.dataUrl, method: result.method, size: getViewportSize(), } } // 方法3: 使用iframe截图(最后的备选方案) const iframe = document.createElement("iframe") iframe.style.position = "absolute" iframe.style.left = "-9999px" iframe.style.width = `${getViewportSize().width}px` iframe.style.height = `${getViewportSize().height}px` iframe.src = page.path document.body.appendChild(iframe) try { await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("iframe加载超时")) }, 15000) iframe.onload = () => { clearTimeout(timeout) setTimeout(resolve, 2000) // 等待渲染完成 } }) // 尝试截图iframe内容 if (iframe.contentDocument && iframe.contentDocument.body) { result = await enhancedScreenshotService.captureElement(iframe.contentDocument.body, { ...screenshotOptions, ...getViewportSize(), }) } } finally { document.body.removeChild(iframe) } if (result?.success && result.dataUrl) { return { ...screenshot, status: "success", dataUrl: result.dataUrl, method: result.method, size: getViewportSize(), } } throw new Error("所有截图方法都失败了") } catch (error) { console.error(`截图失败 ${page.name}:`, error) return { ...screenshot, status: "failed", error: error instanceof Error ? error.message : "未知错误", } } } const startCapture = async () => { if (selectedPages.length === 0) { toast({ title: "请选择页面", description: "请至少选择一个页面进行截图", variant: "destructive", }) return } setIsCapturing(true) setCaptureProgress(0) setScreenshots([]) const selectedPageObjects = pages.filter((p) => selectedPages.includes(p.path)) const newScreenshots: Screenshot[] = [] for (let i = 0; i < selectedPageObjects.length; i++) { const page = selectedPageObjects[i] // 更新进度 setCaptureProgress((i / selectedPageObjects.length) * 100) // 添加待处理的截图 const pendingScreenshot: Screenshot = { id: `${page.path}-${Date.now()}`, name: page.name, url: page.path, timestamp: new Date(), status: "capturing", } newScreenshots.push(pendingScreenshot) setScreenshots([...newScreenshots]) try { const screenshot = await captureScreenshot(page) // 更新截图状态 const updatedScreenshots = [...newScreenshots] updatedScreenshots[i] = screenshot setScreenshots(updatedScreenshots) newScreenshots[i] = screenshot if (screenshot.status === "success") { toast({ title: "截图成功", description: `${page.name} 截图完成`, }) } else { toast({ title: "截图失败", description: `${page.name}: ${screenshot.error}`, variant: "destructive", }) } } catch (error) { console.error(`截图过程出错:`, error) const failedScreenshot: Screenshot = { ...pendingScreenshot, status: "failed", error: error instanceof Error ? error.message : "未知错误", } const updatedScreenshots = [...newScreenshots] updatedScreenshots[i] = failedScreenshot setScreenshots(updatedScreenshots) newScreenshots[i] = failedScreenshot } // 添加延迟避免过快请求 if (i < selectedPageObjects.length - 1) { await new Promise((resolve) => setTimeout(resolve, 1000)) } } setCaptureProgress(100) setIsCapturing(false) const successCount = newScreenshots.filter((s) => s.status === "success").length const failedCount = newScreenshots.filter((s) => s.status === "failed").length toast({ title: "截图完成", description: `成功: ${successCount}, 失败: ${failedCount}`, variant: successCount > 0 ? "default" : "destructive", }) } const retryFailedScreenshots = async () => { const failedScreenshots = screenshots.filter((s) => s.status === "failed") if (failedScreenshots.length === 0) { toast({ title: "没有失败的截图", description: "所有截图都已成功", }) return } setIsCapturing(true) setCaptureProgress(0) for (let i = 0; i < failedScreenshots.length; i++) { const failedScreenshot = failedScreenshots[i] const page = pages.find((p) => p.path === failedScreenshot.url) if (!page) continue setCaptureProgress((i / failedScreenshots.length) * 100) // 更新状态为重新截图中 setScreenshots((prev) => prev.map((s) => (s.id === failedScreenshot.id ? { ...s, status: "capturing" as const } : s)), ) try { const newScreenshot = await captureScreenshot(page) setScreenshots((prev) => prev.map((s) => (s.id === failedScreenshot.id ? newScreenshot : s))) if (newScreenshot.status === "success") { toast({ title: "重试成功", description: `${page.name} 截图完成`, }) } } catch (error) { console.error(`重试截图失败:`, error) setScreenshots((prev) => prev.map((s) => s.id === failedScreenshot.id ? { ...s, status: "failed" as const, error: error instanceof Error ? error.message : "未知错误" } : s, ), ) } await new Promise((resolve) => setTimeout(resolve, 1000)) } setCaptureProgress(100) setIsCapturing(false) } const generateDocument = async () => { const successfulScreenshots = screenshots.filter((s) => s.status === "success" && s.dataUrl) if (successfulScreenshots.length === 0) { toast({ title: "没有可用的截图", description: "请先成功截图至少一个页面", variant: "destructive", }) return } setIsGeneratingDoc(true) try { const docBuffer = await generateDocx(successfulScreenshots, documentSettings) // 下载文档 const blob = new Blob([docBuffer], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = `${documentSettings.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")}_${new Date().toISOString().split("T")[0]}.docx` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) toast({ title: "文档生成成功", description: `已生成包含 ${successfulScreenshots.length} 个截图的文档`, }) } catch (error) { console.error("文档生成失败:", error) toast({ title: "文档生成失败", description: error instanceof Error ? error.message : "未知错误", variant: "destructive", }) } finally { setIsGeneratingDoc(false) } } const deleteScreenshot = (id: string) => { setScreenshots((prev) => prev.filter((s) => s.id !== id)) toast({ title: "截图已删除", description: "截图已从列表中移除", }) } const clearAllScreenshots = () => { setScreenshots([]) toast({ title: "已清空所有截图", description: "所有截图已从列表中移除", }) } const previewPage = (url: string) => { setCurrentPreviewUrl(url) } const getStatusIcon = (status: Screenshot["status"]) => { switch (status) { case "success": return case "failed": return case "capturing": return default: return } } const getPreviewIcon = () => { switch (previewMode) { case "mobile": return case "tablet": return default: return } } return (

文档生成器

自动截图并生成系统文档

增强版截图
页面截图 预览页面 截图管理 文档设置
选择页面 选择需要截图的页面,支持多选
已选择 {selectedPages.length} / {pages.length} 个页面
{pages.map((page) => (
handlePageSelection(page.path, checked as boolean)} />
{page.name}
{page.path}
{page.description && (
{page.description}
)}
))}
截图设置
setScreenshotOptions((prev) => ({ ...prev, quality: Number.parseFloat(e.target.value), })) } className="w-full" />
setScreenshotOptions((prev) => ({ ...prev, scale: Number.parseFloat(e.target.value), })) } className="w-full" />
开始截图 {isCapturing && (
截图进度 {Math.round(captureProgress)}%
)}
{screenshots.some((s) => s.status === "failed") && ( )}
总页面数: {pages.length}
已选择: {selectedPages.length}
已截图: {screenshots.length}
{getPreviewIcon()} 页面预览
预览页面在不同设备上的显示效果
{currentPreviewUrl ? (