"use client" import { useState, useRef } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" import { bookData } from "@/lib/book-data" import { FileText, BookOpen, Settings2, ChevronRight, CheckCircle, Edit3, Save, X, RefreshCw, Link2, Download, Upload, Eye, Database, } from "lucide-react" interface EditingSection { id: string title: string price: number content?: string filePath?: string } export default function ContentPage() { const [expandedParts, setExpandedParts] = useState(["part-1"]) const [editingSection, setEditingSection] = useState(null) const [isSyncing, setIsSyncing] = useState(false) const [isExporting, setIsExporting] = useState(false) const [isImporting, setIsImporting] = useState(false) const [isInitializing, setIsInitializing] = useState(false) const [feishuDocUrl, setFeishuDocUrl] = useState("") const [showFeishuModal, setShowFeishuModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false) const [importData, setImportData] = useState("") const [isLoadingContent, setIsLoadingContent] = useState(false) const fileInputRef = useRef(null) const togglePart = (partId: string) => { setExpandedParts((prev) => (prev.includes(partId) ? prev.filter((id) => id !== partId) : [...prev, partId])) } const totalSections = bookData.reduce( (sum, part) => sum + part.chapters.reduce((cSum, ch) => cSum + ch.sections.length, 0), 0, ) // 读取章节内容 const handleReadSection = async (section: { id: string; title: string; price: number; filePath: string }) => { setIsLoadingContent(true) try { const res = await fetch(`/api/db/book?action=read&id=${section.id}`) const data = await res.json() if (data.success) { setEditingSection({ id: section.id, title: section.title, price: section.price, content: data.section.content || "", filePath: section.filePath, }) } else { // 如果API失败,设置空内容 setEditingSection({ id: section.id, title: section.title, price: section.price, content: "", filePath: section.filePath, }) alert("无法读取文件内容: " + (data.error || "未知错误")) } } catch (error) { console.error("Read section error:", error) setEditingSection({ id: section.id, title: section.title, price: section.price, content: "", filePath: section.filePath, }) } finally { setIsLoadingContent(false) } } // 保存章节 const handleSaveSection = async () => { if (!editingSection) return try { const res = await fetch('/api/db/book', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: editingSection.id, title: editingSection.title, price: editingSection.price, content: editingSection.content, saveToFile: true, // 同时保存到文件系统 }) }) const data = await res.json() if (data.success) { alert(`已保存章节: ${editingSection.title}`) setEditingSection(null) } else { alert("保存失败: " + (data.error || "未知错误")) } } catch (error) { console.error("Save section error:", error) alert("保存失败") } } // 同步到数据库 const handleSyncToDatabase = async () => { setIsSyncing(true) try { const res = await fetch('/api/db/book', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'sync' }) }) const data = await res.json() if (data.success) { alert(data.message) } else { alert("同步失败: " + (data.error || "未知错误")) } } catch (error) { console.error("Sync error:", error) alert("同步失败") } finally { setIsSyncing(false) } } // 导出所有章节 const handleExport = async () => { setIsExporting(true) try { const res = await fetch('/api/db/book?action=export') const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `book_sections_${new Date().toISOString().split('T')[0]}.json` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) alert("导出成功") } catch (error) { console.error("Export error:", error) alert("导出失败") } finally { setIsExporting(false) } } // 导入章节 const handleImport = async () => { if (!importData) { alert("请输入或上传JSON数据") return } setIsImporting(true) try { const data = JSON.parse(importData) const res = await fetch('/api/db/book', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'import', data }) }) const result = await res.json() if (result.success) { alert(result.message) setShowImportModal(false) setImportData("") } else { alert("导入失败: " + (result.error || "未知错误")) } } catch (error) { console.error("Import error:", error) alert("导入失败: JSON格式错误") } finally { setIsImporting(false) } } // 文件上传 const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = (event) => { const content = event.target?.result as string const fileName = file.name.toLowerCase() // 根据文件类型处理 if (fileName.endsWith('.json')) { // JSON文件直接使用 setImportData(content) } else if (fileName.endsWith('.txt') || fileName.endsWith('.md') || fileName.endsWith('.markdown')) { // TXT/MD文件自动解析为JSON格式 const parsedData = parseTxtToJson(content, file.name) setImportData(JSON.stringify(parsedData, null, 2)) } else { setImportData(content) } } reader.readAsText(file) } // 解析TXT/MD文件为JSON格式 const parseTxtToJson = (content: string, fileName: string) => { const lines = content.split('\n') const sections: any[] = [] let currentSection: any = null let currentContent: string[] = [] let sectionIndex = 1 for (const line of lines) { // 检测标题行(以#开头或数字+点开头) const titleMatch = line.match(/^#+\s+(.+)$/) || line.match(/^(\d+[\.\、]\s*.+)$/) if (titleMatch) { // 保存前一个章节 if (currentSection) { currentSection.content = currentContent.join('\n').trim() if (currentSection.content) { sections.push(currentSection) } } // 开始新章节 currentSection = { id: `import-${sectionIndex}`, title: titleMatch[1].replace(/^#+\s*/, '').trim(), price: 1, is_free: sectionIndex <= 3, // 前3章免费 } currentContent = [] sectionIndex++ } else if (currentSection) { currentContent.push(line) } else if (line.trim()) { // 没有标题但有内容,创建默认章节 currentSection = { id: `import-${sectionIndex}`, title: fileName.replace(/\.(txt|md|markdown)$/i, ''), price: 1, is_free: true, } currentContent.push(line) sectionIndex++ } } // 保存最后一个章节 if (currentSection) { currentSection.content = currentContent.join('\n').trim() if (currentSection.content) { sections.push(currentSection) } } return sections } // 初始化数据库 const handleInitDatabase = async () => { if (!confirm("确定要初始化数据库吗?这将创建所有必需的表结构。")) return setIsInitializing(true) try { const res = await fetch('/api/db/init', { method: 'POST' }) const data = await res.json() if (data.success) { alert(data.message) } else { alert("初始化失败: " + (data.error || "未知错误")) } } catch (error) { console.error("Init database error:", error) alert("初始化失败") } finally { setIsInitializing(false) } } const handleSyncFeishu = async () => { if (!feishuDocUrl) { alert("请输入飞书文档链接") return } setIsSyncing(true) await new Promise((resolve) => setTimeout(resolve, 2000)) setIsSyncing(false) setShowFeishuModal(false) alert("飞书文档同步成功!") } return (

内容管理

共 {bookData.length} 篇 · {totalSections} 节内容

{/* 导入弹窗 */} 导入章节数据

• JSON格式: 直接导入章节数据
• TXT/MD格式: 自动解析为章节内容