Files
users/app/documentation/page.tsx
v0 2408d50cb0 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>
2025-07-18 13:47:12 +00:00

1052 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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<Screenshot[]>([])
const [selectedPages, setSelectedPages] = useState<string[]>([])
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<DocumentSettings>({
title: "用户数据资产中台文档",
author: "系统管理员",
description: "自动生成的系统文档",
includeTimestamp: true,
includePageUrls: true,
imageFormat: "png",
imageQuality: 0.9,
pageSize: "A4",
orientation: "portrait",
})
const previewRef = useRef<HTMLIFrameElement>(null)
const [screenshotOptions, setScreenshotOptions] = useState<ScreenshotOptions>({
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<Screenshot> => {
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 <CheckCircle className="h-4 w-4 text-green-500" />
case "failed":
return <XCircle className="h-4 w-4 text-red-500" />
case "capturing":
return <Clock className="h-4 w-4 text-blue-500 animate-spin" />
default:
return <Clock className="h-4 w-4 text-gray-400" />
}
}
const getPreviewIcon = () => {
switch (previewMode) {
case "mobile":
return <Smartphone className="h-4 w-4" />
case "tablet":
return <Tablet className="h-4 w-4" />
default:
return <Monitor className="h-4 w-4" />
}
}
return (
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="text-muted-foreground mt-1"></p>
</div>
<div className="flex items-center space-x-2">
<Badge variant="outline" className="flex items-center space-x-1">
<Zap className="h-3 w-3" />
<span></span>
</Badge>
</div>
</div>
<Tabs defaultValue="capture" className="space-y-6">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="capture"></TabsTrigger>
<TabsTrigger value="preview"></TabsTrigger>
<TabsTrigger value="screenshots"></TabsTrigger>
<TabsTrigger value="settings"></TabsTrigger>
</TabsList>
<TabsContent value="capture" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Globe className="h-5 w-5" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm" onClick={selectAllPages}>
</Button>
<Button variant="outline" size="sm" onClick={deselectAllPages}>
</Button>
</div>
<Badge variant="secondary">
{selectedPages.length} / {pages.length}
</Badge>
</div>
<ScrollArea className="h-[400px]">
<div className="space-y-2">
{pages.map((page) => (
<div
key={page.path}
className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50"
>
<Checkbox
checked={selectedPages.includes(page.path)}
onCheckedChange={(checked) => handlePageSelection(page.path, checked as boolean)}
/>
<div className="flex-1 min-w-0">
<div className="font-medium">{page.name}</div>
<div className="text-sm text-muted-foreground truncate">{page.path}</div>
{page.description && (
<div className="text-xs text-muted-foreground mt-1">{page.description}</div>
)}
</div>
<Button variant="ghost" size="sm" onClick={() => previewPage(page.path)}>
<Eye className="h-4 w-4" />
</Button>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Settings className="h-5 w-5" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Select
value={previewMode}
onValueChange={(value: "desktop" | "tablet" | "mobile") => setPreviewMode(value)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="desktop">
<div className="flex items-center space-x-2">
<Monitor className="h-4 w-4" />
<span> (1920x1080)</span>
</div>
</SelectItem>
<SelectItem value="tablet">
<div className="flex items-center space-x-2">
<Tablet className="h-4 w-4" />
<span> (768x1024)</span>
</div>
</SelectItem>
<SelectItem value="mobile">
<div className="flex items-center space-x-2">
<Smartphone className="h-4 w-4" />
<span> (375x667)</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Select
value={screenshotOptions.format}
onValueChange={(value: "png" | "jpeg" | "webp") =>
setScreenshotOptions((prev) => ({ ...prev, format: value }))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="png">PNG ()</SelectItem>
<SelectItem value="jpeg">JPEG ()</SelectItem>
<SelectItem value="webp">WebP ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">
: {Math.round((screenshotOptions.quality || 0.9) * 100)}%
</label>
<input
type="range"
min="0.1"
max="1"
step="0.1"
value={screenshotOptions.quality || 0.9}
onChange={(e) =>
setScreenshotOptions((prev) => ({
...prev,
quality: Number.parseFloat(e.target.value),
}))
}
className="w-full"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">: {screenshotOptions.scale || 1}x</label>
<input
type="range"
min="0.5"
max="3"
step="0.1"
value={screenshotOptions.scale || 1}
onChange={(e) =>
setScreenshotOptions((prev) => ({
...prev,
scale: Number.parseFloat(e.target.value),
}))
}
className="w-full"
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Camera className="h-5 w-5" />
<span></span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{isCapturing && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span></span>
<span>{Math.round(captureProgress)}%</span>
</div>
<Progress value={captureProgress} />
</div>
)}
<div className="space-y-2">
<Button
onClick={startCapture}
disabled={isCapturing || selectedPages.length === 0}
className="w-full"
>
{isCapturing ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
<>
<Camera className="mr-2 h-4 w-4" />
</>
)}
</Button>
{screenshots.some((s) => s.status === "failed") && (
<Button
variant="outline"
onClick={retryFailedScreenshots}
disabled={isCapturing}
className="w-full bg-transparent"
>
<RefreshCw className="mr-2 h-4 w-4" />
</Button>
)}
</div>
<div className="text-sm text-muted-foreground">
<div className="flex items-center justify-between">
<span>:</span>
<span>{pages.length}</span>
</div>
<div className="flex items-center justify-between">
<span>:</span>
<span>{selectedPages.length}</span>
</div>
<div className="flex items-center justify-between">
<span>:</span>
<span>{screenshots.length}</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
<TabsContent value="preview" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{getPreviewIcon()}
<span></span>
</div>
<div className="flex items-center space-x-2">
<Select
value={previewMode}
onValueChange={(value: "desktop" | "tablet" | "mobile") => setPreviewMode(value)}
>
<SelectTrigger className="w-[150px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="desktop"></SelectItem>
<SelectItem value="tablet"></SelectItem>
<SelectItem value="mobile"></SelectItem>
</SelectContent>
</Select>
</div>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="border rounded-lg overflow-hidden bg-gray-100 p-4">
<div
className="mx-auto bg-white shadow-lg rounded-lg overflow-hidden"
style={{
width: `${getViewportSize().width}px`,
height: `${getViewportSize().height}px`,
maxWidth: "100%",
transform: "scale(0.5)",
transformOrigin: "top center",
}}
>
{currentPreviewUrl ? (
<iframe
ref={previewRef}
src={currentPreviewUrl}
className="w-full h-full border-0"
title="页面预览"
/>
) : (
<div className="flex items-center justify-center h-full text-muted-foreground">
<div className="text-center">
<Globe className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p></p>
</div>
</div>
)}
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="screenshots" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<ImageIcon className="h-5 w-5" />
<span></span>
</div>
<div className="flex items-center space-x-2">
<Badge variant="outline">{screenshots.filter((s) => s.status === "success").length} </Badge>
<Badge variant="destructive">{screenshots.filter((s) => s.status === "failed").length} </Badge>
{screenshots.length > 0 && (
<Button variant="outline" size="sm" onClick={clearAllScreenshots}>
<Trash2 className="h-4 w-4 mr-1" />
</Button>
)}
</div>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{screenshots.length === 0 ? (
<div className="text-center py-12 text-muted-foreground">
<ImageIcon className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p></p>
<p className="text-sm">"页面截图"</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{screenshots.map((screenshot) => (
<Card key={screenshot.id} className="overflow-hidden">
<div className="aspect-video bg-gray-100 relative">
{screenshot.dataUrl ? (
<img
src={screenshot.dataUrl || "/placeholder.svg"}
alt={screenshot.name}
className="w-full h-full object-cover"
/>
) : (
<div className="flex items-center justify-center h-full">
{screenshot.status === "capturing" ? (
<RefreshCw className="h-8 w-8 animate-spin text-blue-500" />
) : (
<ImageIcon className="h-8 w-8 text-gray-400" />
)}
</div>
)}
<div className="absolute top-2 right-2">{getStatusIcon(screenshot.status)}</div>
</div>
<CardContent className="p-4">
<div className="space-y-2">
<div className="font-medium truncate">{screenshot.name}</div>
<div className="text-sm text-muted-foreground truncate">{screenshot.url}</div>
{screenshot.method && (
<Badge variant="outline" className="text-xs">
{screenshot.method}
</Badge>
)}
{screenshot.error && <div className="text-xs text-red-500 truncate">{screenshot.error}</div>}
<div className="text-xs text-muted-foreground">{screenshot.timestamp.toLocaleString()}</div>
<div className="flex items-center justify-between">
<div className="text-xs text-muted-foreground">
{screenshot.size && `${screenshot.size.width}×${screenshot.size.height}`}
</div>
<Button variant="ghost" size="sm" onClick={() => deleteScreenshot(screenshot.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
)}
</CardContent>
</Card>
{screenshots.some((s) => s.status === "success") && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<FileText className="h-5 w-5" />
<span></span>
</CardTitle>
<CardDescription>Word文档</CardDescription>
</CardHeader>
<CardContent>
<Button onClick={generateDocument} disabled={isGeneratingDoc} className="w-full">
{isGeneratingDoc ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
<>
<Download className="mr-2 h-4 w-4" />
Word文档
</>
)}
</Button>
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="settings" className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Settings className="h-5 w-5" />
<span></span>
</CardTitle>
<CardDescription>Word文档格式和内容</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Input
value={documentSettings.title}
onChange={(e) =>
setDocumentSettings((prev) => ({
...prev,
title: e.target.value,
}))
}
placeholder="输入文档标题"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Input
value={documentSettings.author}
onChange={(e) =>
setDocumentSettings((prev) => ({
...prev,
author: e.target.value,
}))
}
placeholder="输入作者姓名"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Textarea
value={documentSettings.description}
onChange={(e) =>
setDocumentSettings((prev) => ({
...prev,
description: e.target.value,
}))
}
placeholder="输入文档描述"
rows={3}
/>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Select
value={documentSettings.pageSize}
onValueChange={(value: "A4" | "A3" | "Letter") =>
setDocumentSettings((prev) => ({ ...prev, pageSize: value }))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="A4">A4</SelectItem>
<SelectItem value="A3">A3</SelectItem>
<SelectItem value="Letter">Letter</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<Select
value={documentSettings.orientation}
onValueChange={(value: "portrait" | "landscape") =>
setDocumentSettings((prev) => ({ ...prev, orientation: value }))
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="portrait"></SelectItem>
<SelectItem value="landscape"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<label className="text-sm font-medium"></label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
checked={documentSettings.includeTimestamp}
onCheckedChange={(checked) =>
setDocumentSettings((prev) => ({
...prev,
includeTimestamp: checked as boolean,
}))
}
/>
<label className="text-sm"></label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
checked={documentSettings.includePageUrls}
onCheckedChange={(checked) =>
setDocumentSettings((prev) => ({
...prev,
includePageUrls: checked as boolean,
}))
}
/>
<label className="text-sm">URL</label>
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}