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:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

View 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>
)
}

View 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>
)
}

View 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>
)
}