diff --git a/app/admin/content/page.tsx b/app/admin/content/page.tsx index 3b0b44f..d7a1f7a 100644 --- a/app/admin/content/page.tsx +++ b/app/admin/content/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useRef } from "react" +import { useState, useRef, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -26,6 +26,10 @@ import { Upload, Eye, Database, + Plus, + Image as ImageIcon, + Trash2, + Search, } from "lucide-react" interface EditingSection { @@ -34,6 +38,9 @@ interface EditingSection { price: number content?: string filePath?: string + isNew?: boolean + partId?: string + chapterId?: string } export default function ContentPage() { @@ -46,9 +53,26 @@ export default function ContentPage() { const [feishuDocUrl, setFeishuDocUrl] = useState("") const [showFeishuModal, setShowFeishuModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false) + const [showNewSectionModal, setShowNewSectionModal] = useState(false) const [importData, setImportData] = useState("") const [isLoadingContent, setIsLoadingContent] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [searchQuery, setSearchQuery] = useState("") + const [searchResults, setSearchResults] = useState([]) + const [isSearching, setIsSearching] = useState(false) + const [uploadingImage, setUploadingImage] = useState(false) const fileInputRef = useRef(null) + const imageInputRef = useRef(null) + + // 新建章节表单 + const [newSection, setNewSection] = useState({ + id: "", + title: "", + price: 1, + partId: "part-1", + chapterId: "chapter-1", + content: "", + }) const togglePart = (partId: string) => { setExpandedParts((prev) => (prev.includes(partId) ? prev.filter((id) => id !== partId) : [...prev, partId])) @@ -69,8 +93,8 @@ export default function ContentPage() { if (data.success) { setEditingSection({ id: section.id, - title: section.title, - price: section.price, + title: data.section.title || section.title, + price: data.section.price || section.price, content: data.section.content || "", filePath: section.filePath, }) @@ -103,6 +127,7 @@ export default function ContentPage() { const handleSaveSection = async () => { if (!editingSection) return + setIsSaving(true) try { const res = await fetch('/api/db/book', { method: 'PUT', @@ -126,6 +151,110 @@ export default function ContentPage() { } catch (error) { console.error("Save section error:", error) alert("保存失败") + } finally { + setIsSaving(false) + } + } + + // 创建新章节 + const handleCreateSection = async () => { + if (!newSection.id || !newSection.title) { + alert("请填写章节ID和标题") + return + } + + setIsSaving(true) + try { + const res = await fetch('/api/db/book', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: newSection.id, + title: newSection.title, + price: newSection.price, + content: newSection.content, + partId: newSection.partId, + chapterId: newSection.chapterId, + saveToFile: false, // 新建章节暂不保存到文件系统 + }) + }) + + const data = await res.json() + if (data.success) { + alert(`章节创建成功: ${newSection.title}`) + setShowNewSectionModal(false) + setNewSection({ id: "", title: "", price: 1, partId: "part-1", chapterId: "chapter-1", content: "" }) + } else { + alert("创建失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Create section error:", error) + alert("创建失败") + } finally { + setIsSaving(false) + } + } + + // 上传图片 + const handleImageUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + + setUploadingImage(true) + try { + const formData = new FormData() + formData.append('file', file) + formData.append('folder', 'book-images') + + const res = await fetch('/api/upload', { + method: 'POST', + body: formData + }) + + const data = await res.json() + if (data.success) { + // 插入图片Markdown到内容 + const imageMarkdown = `![${file.name}](${data.data.url})` + if (editingSection) { + setEditingSection({ + ...editingSection, + content: (editingSection.content || '') + '\n\n' + imageMarkdown + }) + } + alert(`图片上传成功: ${data.data.url}`) + } else { + alert("上传失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Image upload error:", error) + alert("上传失败") + } finally { + setUploadingImage(false) + if (imageInputRef.current) { + imageInputRef.current.value = '' + } + } + } + + // 搜索内容 + const handleSearch = async () => { + if (!searchQuery.trim()) return + + setIsSearching(true) + try { + const res = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`) + const data = await res.json() + + if (data.success) { + setSearchResults(data.data.results || []) + } else { + alert("搜索失败: " + (data.error || "未知错误")) + } + } catch (error) { + console.error("Search error:", error) + alert("搜索失败") + } finally { + setIsSearching(false) } } @@ -297,11 +426,15 @@ export default function ContentPage() { setIsInitializing(true) try { - const res = await fetch('/api/db/init', { method: 'POST' }) + const res = await fetch('/api/db/init', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ adminToken: 'init_db_2025' }) + }) const data = await res.json() if (data.success) { - alert(data.message) + alert(data.data?.message || '初始化成功') } else { alert("初始化失败: " + (data.error || "未知错误")) } @@ -506,6 +639,116 @@ export default function ContentPage() { + {/* 新建章节弹窗 */} + + + + + + 新建章节 + + +
+
+
+ + setNewSection({ ...newSection, id: e.target.value })} + /> +
+
+ + setNewSection({ ...newSection, price: Number(e.target.value) })} + /> +
+
+
+ + setNewSection({ ...newSection, title: e.target.value })} + /> +
+
+
+ + +
+
+ + +
+
+
+ +