From 4dd2f9f4a7369d1d570890363db7b86b15fe34d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E8=8B=A5?= Date: Sun, 25 Jan 2026 19:37:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E7=AE=A1=E7=90=86+=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD+?= =?UTF-8?q?=E5=88=86=E9=94=80=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更新: - 后台菜单精简(9项→6项) - 新增搜索功能(敏感信息过滤) - 分销绑定和提现系统完善 - 数据库初始化API(自动修复表结构) - 用户管理:显示绑定关系详情 - 小程序:上下章导航优化、匹配页面重构 - 修复hydration和数据类型问题 --- app/admin/content/page.tsx | 403 +++++++++++++- app/admin/layout.tsx | 40 +- app/admin/match/page.tsx | 517 ++++++++++++++++++ app/admin/users/page.tsx | 226 +++++++- app/admin/withdrawals/page.tsx | 425 +++++++++----- app/api/admin/withdrawals/route.ts | 169 ++++++ app/api/book/search/route.ts | 150 +++++ app/api/db/book/route.ts | 425 ++++++++++++++ app/api/db/config/route.ts | 300 ++++++++++ app/api/db/init/route.ts | 222 +++++--- app/api/db/users/referrals/route.ts | 105 ++++ app/api/db/users/route.ts | 262 +++++++++ app/api/match/config/route.ts | 201 ++----- app/api/miniprogram/login/route.ts | 95 +++- app/api/referral/bind/route.ts | 201 +++++++ app/api/referral/data/route.ts | 189 ++++--- app/api/search/route.ts | 273 +++++++++ app/api/upload/route.ts | 134 +++++ app/api/user/profile/route.ts | 170 ++++++ app/api/wechat/login/route.ts | 108 +++- app/api/withdraw/route.ts | 235 ++++++++ app/chapters/page.tsx | 16 +- app/page.tsx | 10 +- .../1.1 荷包:电动车出租的被动收入模式.md | 2 - components/search-modal.tsx | 212 +++++++ lib/db.ts | 50 +- lib/store.ts | 3 - miniprogram/app.js | 80 ++- miniprogram/app.json | 5 +- miniprogram/pages/about/about.js | 20 +- miniprogram/pages/about/about.wxml | 20 +- miniprogram/pages/chapters/chapters.js | 2 +- miniprogram/pages/index/index.js | 70 ++- miniprogram/pages/index/index.wxml | 8 +- miniprogram/pages/match/match.js | 19 +- miniprogram/pages/match/match.wxml | 48 +- miniprogram/pages/match/match.wxss | 192 +++++++ miniprogram/pages/my/my.js | 2 +- miniprogram/pages/read/read.js | 30 +- miniprogram/pages/read/read.wxml | 34 +- miniprogram/pages/referral/referral.js | 93 +++- miniprogram/pages/search/search.js | 87 +++ miniprogram/pages/search/search.json | 5 + miniprogram/pages/search/search.wxml | 92 ++++ miniprogram/pages/search/search.wxss | 264 +++++++++ miniprogram/pages/settings/settings.js | 124 ++++- miniprogram/project.config.json | 2 +- tsconfig.tsbuildinfo | 1 + 开发文档/TDD_创业派对项目方案_v1.0.md | 216 ++++++++ 49 files changed, 5921 insertions(+), 636 deletions(-) create mode 100644 app/admin/match/page.tsx create mode 100644 app/api/admin/withdrawals/route.ts create mode 100644 app/api/book/search/route.ts create mode 100644 app/api/db/book/route.ts create mode 100644 app/api/db/config/route.ts create mode 100644 app/api/db/users/referrals/route.ts create mode 100644 app/api/db/users/route.ts create mode 100644 app/api/referral/bind/route.ts create mode 100644 app/api/search/route.ts create mode 100644 app/api/upload/route.ts create mode 100644 app/api/user/profile/route.ts create mode 100644 app/api/withdraw/route.ts create mode 100644 components/search-modal.tsx create mode 100644 miniprogram/pages/search/search.js create mode 100644 miniprogram/pages/search/search.json create mode 100644 miniprogram/pages/search/search.wxml create mode 100644 miniprogram/pages/search/search.wxss create mode 100644 tsconfig.tsbuildinfo create mode 100644 开发文档/TDD_创业派对项目方案_v1.0.md 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 })} + /> +
+
+
+ + +
+
+ + +
+
+
+ +