refactor: overhaul UI for streamlined user experience
Redesign navigation, home overview, user portrait, and valuation pages with improved functionality and responsive design. Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
176
components/documentation/document-generator.tsx
Normal file
176
components/documentation/document-generator.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Loader2, FileText, AlertTriangle } from "lucide-react"
|
||||
import { generateDocx } from "@/lib/documentation/docx-generator"
|
||||
|
||||
interface DocumentSection {
|
||||
title: string
|
||||
path: string
|
||||
description: string
|
||||
screenshot: string
|
||||
}
|
||||
|
||||
interface DocumentGeneratorProps {
|
||||
pages: Array<{ path: string; title: string; description: string }>
|
||||
screenshots: Record<string, string>
|
||||
onDocumentGenerated: (url: string) => void
|
||||
}
|
||||
|
||||
export default function DocumentGenerator({ pages, screenshots, onDocumentGenerated }: DocumentGeneratorProps) {
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [sections, setSections] = useState<DocumentSection[]>([])
|
||||
const [descriptions, setDescriptions] = useState<Record<string, string>>({})
|
||||
|
||||
// 初始化页面描述
|
||||
useEffect(() => {
|
||||
const initialDescriptions: Record<string, string> = {}
|
||||
pages.forEach((page) => {
|
||||
initialDescriptions[page.path] = page.description || ""
|
||||
})
|
||||
setDescriptions(initialDescriptions)
|
||||
}, [pages])
|
||||
|
||||
// 更新页面描述
|
||||
const updateDescription = (path: string, description: string) => {
|
||||
setDescriptions((prev) => ({
|
||||
...prev,
|
||||
[path]: description,
|
||||
}))
|
||||
}
|
||||
|
||||
// 准备文档部分
|
||||
useEffect(() => {
|
||||
const newSections = pages
|
||||
.filter((page) => screenshots[page.path])
|
||||
.map((page) => ({
|
||||
title: page.title,
|
||||
path: page.path,
|
||||
description: descriptions[page.path] || page.description || "",
|
||||
screenshot: screenshots[page.path],
|
||||
}))
|
||||
|
||||
setSections(newSections)
|
||||
}, [pages, screenshots, descriptions])
|
||||
|
||||
// 生成文档
|
||||
const generateDocument = async () => {
|
||||
if (sections.length === 0) {
|
||||
setError("没有可用的截图,请先捕获页面截图")
|
||||
return
|
||||
}
|
||||
|
||||
setIsGenerating(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
console.log("开始生成文档...")
|
||||
console.log("文档部分数量:", sections.length)
|
||||
|
||||
// 准备文档数据
|
||||
const documentData = {
|
||||
title: "用户数据资产中台使用手册",
|
||||
author: "系统管理员",
|
||||
date: new Date().toLocaleDateString("zh-CN"),
|
||||
sections: sections,
|
||||
}
|
||||
|
||||
console.log("文档数据准备完成")
|
||||
|
||||
// 生成Word文档
|
||||
const docxUrl = await generateDocx(documentData)
|
||||
console.log("文档生成成功:", docxUrl)
|
||||
|
||||
// 调用回调函数
|
||||
onDocumentGenerated(docxUrl)
|
||||
} catch (error) {
|
||||
console.error("生成文档时出错:", error)
|
||||
setError(`生成文档时出错: ${error instanceof Error ? error.message : String(error)}`)
|
||||
} finally {
|
||||
setIsGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold">文档内容编辑</h2>
|
||||
<Button
|
||||
onClick={generateDocument}
|
||||
disabled={isGenerating || sections.length === 0}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{isGenerating ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
生成中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FileText className="h-4 w-4" />
|
||||
生成文档
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{sections.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">没有可用的截图</p>
|
||||
<p className="text-sm text-gray-400 mt-1">请先捕获页面截图</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
已捕获 {sections.length} 个页面的截图。您可以编辑每个页面的描述,然后生成文档。
|
||||
</p>
|
||||
|
||||
{sections.map((section) => (
|
||||
<Card key={section.path} className="overflow-hidden">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="md:w-1/3">
|
||||
<div className="border rounded-md overflow-hidden">
|
||||
<img
|
||||
src={section.screenshot || "/placeholder.svg"}
|
||||
alt={section.title}
|
||||
className="w-full h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm font-medium mt-2">{section.title}</p>
|
||||
<p className="text-xs text-gray-500">{section.path}</p>
|
||||
</div>
|
||||
<div className="md:w-2/3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`desc-${section.path}`}>页面描述</Label>
|
||||
<Textarea
|
||||
id={`desc-${section.path}`}
|
||||
value={descriptions[section.path] || ""}
|
||||
onChange={(e) => updateDescription(section.path, e.target.value)}
|
||||
placeholder="输入此页面的详细描述"
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
100
components/documentation/export-controls.tsx
Normal file
100
components/documentation/export-controls.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { FileDown, FileText, Copy } from "lucide-react"
|
||||
|
||||
interface ExportControlsProps {
|
||||
documentUrl: string | null
|
||||
onError: (error: string) => void
|
||||
}
|
||||
|
||||
export default function ExportControls({ documentUrl, onError }: ExportControlsProps) {
|
||||
const handleDownload = () => {
|
||||
if (!documentUrl) {
|
||||
onError("没有可下载的文档")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建一个临时链接并触发下载
|
||||
const link = document.createElement("a")
|
||||
link.href = documentUrl
|
||||
link.download = "用户数据资产中台使用手册.docx"
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
} catch (err) {
|
||||
onError(`下载文档时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePreview = () => {
|
||||
if (!documentUrl) {
|
||||
onError("没有可预览的文档")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 在新窗口中打开文档
|
||||
window.open(documentUrl, "_blank")
|
||||
} catch (err) {
|
||||
onError(`预览文档时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyLink = () => {
|
||||
if (!documentUrl) {
|
||||
onError("没有可复制的文档链接")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 复制链接到剪贴板
|
||||
navigator.clipboard
|
||||
.writeText(documentUrl)
|
||||
.then(() => {
|
||||
alert("文档链接已复制到剪贴板")
|
||||
})
|
||||
.catch((err) => {
|
||||
onError(`复制链接时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
})
|
||||
} catch (err) {
|
||||
onError(`复制链接时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{documentUrl ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<Button onClick={handleDownload} className="flex items-center gap-2">
|
||||
<FileDown className="h-4 w-4" />
|
||||
<span>下载Word文档</span>
|
||||
</Button>
|
||||
|
||||
<Button onClick={handlePreview} variant="outline" className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
<span>预览文档</span>
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleCopyLink} variant="outline" className="flex items-center gap-2">
|
||||
<Copy className="h-4 w-4" />
|
||||
<span>复制链接</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-gray-50 rounded-md border text-sm">
|
||||
<p className="font-medium mb-2">文档已生成</p>
|
||||
<p className="text-gray-500 break-all">{documentUrl}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p>尚未生成文档</p>
|
||||
<p className="text-sm mt-2">请先完成文档生成步骤</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
119
components/documentation/screenshot-capture.tsx
Normal file
119
components/documentation/screenshot-capture.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { captureScreenshot } from "@/lib/documentation/screenshot-service"
|
||||
|
||||
interface ScreenshotCaptureProps {
|
||||
pages: Array<{ path: string; title: string; description: string }>
|
||||
isCapturing: boolean
|
||||
onScreenshotCaptured: (path: string, dataUrl: string) => void
|
||||
onCaptureComplete: () => void
|
||||
onError: (error: string) => void
|
||||
}
|
||||
|
||||
export default function ScreenshotCapture({
|
||||
pages,
|
||||
isCapturing,
|
||||
onScreenshotCaptured,
|
||||
onCaptureComplete,
|
||||
onError,
|
||||
}: ScreenshotCaptureProps) {
|
||||
const [currentPageIndex, setCurrentPageIndex] = useState(-1)
|
||||
const [currentStatus, setCurrentStatus] = useState("")
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||
const baseUrl = typeof window !== "undefined" ? window.location.origin : ""
|
||||
|
||||
useEffect(() => {
|
||||
if (isCapturing && pages.length > 0) {
|
||||
// 开始捕获过程
|
||||
setCurrentPageIndex(0)
|
||||
} else if (!isCapturing) {
|
||||
setCurrentPageIndex(-1)
|
||||
setCurrentStatus("")
|
||||
}
|
||||
}, [isCapturing, pages])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPageIndex >= 0 && currentPageIndex < pages.length) {
|
||||
const capturePage = async () => {
|
||||
const page = pages[currentPageIndex]
|
||||
const fullPath = `${baseUrl}${page.path}`
|
||||
|
||||
try {
|
||||
setCurrentStatus(`正在加载页面: ${page.title}`)
|
||||
|
||||
// 等待iframe加载完成
|
||||
if (iframeRef.current) {
|
||||
iframeRef.current.src = fullPath
|
||||
|
||||
// 监听iframe加载完成事件
|
||||
const handleLoad = async () => {
|
||||
try {
|
||||
setCurrentStatus(`正在捕获页面: ${page.title}`)
|
||||
|
||||
// 给页面一些时间完全渲染
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
// 捕获截图
|
||||
const screenshot = await captureScreenshot(iframeRef.current!)
|
||||
onScreenshotCaptured(page.path, screenshot)
|
||||
|
||||
// 移动到下一页
|
||||
if (currentPageIndex < pages.length - 1) {
|
||||
setCurrentPageIndex(currentPageIndex + 1)
|
||||
} else {
|
||||
// 所有页面都已捕获
|
||||
onCaptureComplete()
|
||||
}
|
||||
} catch (err) {
|
||||
onError(`捕获页面 ${page.title} 截图时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
iframeRef.current.onload = handleLoad
|
||||
}
|
||||
} catch (err) {
|
||||
onError(`加载页面 ${page.title} 时出错: ${err instanceof Error ? err.message : String(err)}`)
|
||||
|
||||
// 尝试继续下一页
|
||||
if (currentPageIndex < pages.length - 1) {
|
||||
setCurrentPageIndex(currentPageIndex + 1)
|
||||
} else {
|
||||
onCaptureComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
capturePage()
|
||||
}
|
||||
}, [currentPageIndex, pages, onScreenshotCaptured, onCaptureComplete, onError, baseUrl])
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{isCapturing && currentPageIndex >= 0 && currentPageIndex < pages.length && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-500">{currentStatus}</p>
|
||||
<p className="text-sm font-medium">
|
||||
正在处理: {currentPageIndex + 1} / {pages.length} - {pages[currentPageIndex].title}
|
||||
</p>
|
||||
|
||||
<Card className="overflow-hidden border-2 border-blue-200 bg-blue-50">
|
||||
<div className="relative pt-[56.25%]">
|
||||
{" "}
|
||||
{/* 16:9 宽高比 */}
|
||||
<iframe ref={iframeRef} className="absolute top-0 left-0 w-full h-full" title="页面预览" />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isCapturing && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p>点击"开始捕获"按钮开始截图过程</p>
|
||||
<p className="text-sm mt-2">将依次捕获 {pages.length} 个页面的截图</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user