diff --git a/nkebao/src/App.tsx b/nkebao/src/App.tsx index 8eebd60f..55ce6441 100644 --- a/nkebao/src/App.tsx +++ b/nkebao/src/App.tsx @@ -37,6 +37,7 @@ import TrafficPool from './pages/traffic-pool/TrafficPool'; import ContactImport from './pages/contact-import/ContactImport'; import Content from './pages/content/Content'; import TrafficPoolDetail from './pages/traffic-pool/TrafficPoolDetail'; +import NewContent from './pages/content/NewContent'; function App() { // 初始化HTTP拦截器 @@ -89,6 +90,7 @@ function App() { } /> } /> } /> + } /> {/* 你可以继续添加更多路由 */} diff --git a/nkebao/src/pages/content/Content.tsx b/nkebao/src/pages/content/Content.tsx index eef1c55f..02aea9bb 100644 --- a/nkebao/src/pages/content/Content.tsx +++ b/nkebao/src/pages/content/Content.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical } from 'lucide-react'; +import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical, Copy } from 'lucide-react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -66,6 +66,63 @@ interface ContentLibrary { selectedGroupMembers?: WechatGroupMember[]; } +function CardMenu({ onView, onEdit, onDelete, onViewMaterials }: { + onView: () => void; + onEdit: () => void; + onDelete: () => void; + onViewMaterials: () => void; +}) { + const [open, setOpen] = useState(false); + const menuRef = React.useRef(null); + + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpen(false); + } + } + if (open) document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [open]); + + return ( +
+ + {open && ( +
+
{ onView(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 查看 +
+
{ onEdit(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 编辑 +
+
{ onDelete(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, color: "#e53e3e", transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 删除 +
+
{ onViewMaterials(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 查看素材 +
+
+ )} +
+ ); +} + export default function Content() { const navigate = useNavigate(); const [libraries, setLibraries] = useState([]); @@ -192,147 +249,137 @@ export default function Content() { return ( - -
-
- - setSearchQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - className="pl-9" - /> + <> + +
+
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-9 rounded-full bg-gray-50 border-none focus:ring-2 focus:ring-blue-100" + /> +
+ + +
+
+ + + 全部 + 微信好友 + 聊天群 + + +
- -
- -
- - - 全部 - 微信好友 - 聊天群 - - -
- - } + + } + footer={} >
-
- {loading ? ( -
- -
- ) : filteredLibraries.length === 0 ? ( -
-
-

暂无数据

- -
-
- ) : ( - filteredLibraries.map((library) => ( - -
-
-
-

{library.name}

- - {library.isEnabled === 1 ? '已启用' : '未启用'} - -
-
-
- 来源: - {library.sourceType === 1 && library.sourceFriends?.length > 0 ? ( +
+ {loading ? ( +
+ +
+ ) : filteredLibraries.length === 0 ? ( +
+ 暂无内容库 +
暂无内容库,快去新建一个吧!
+ +
+ ) : ( + filteredLibraries.map((library, idx) => ( + +
+
+
+

{library.name}

+ + {library.isEnabled === 1 ? '已启用' : '未启用'} + +
+
+
+ 来源: + {library.sourceType === 1 && library.sourceFriends?.length > 0 ? ( +
+ {(library.friendsData || []).slice(0, 3).map((friend) => ( + {friend.nickname + ))} + {library.sourceFriends.length > 3 && ( + + +{library.sourceFriends.length - 3} + + )} +
+ ) : library.sourceType === 2 && library.sourceGroups?.length > 0 ? ( +
- {(library.friendsData || []).slice(0, 3).map((friend) => ( + {(library.groupsData || []).slice(0, 3).map((group) => ( {friend.nickname ))} - {library.sourceFriends.length > 3 && ( + {library.sourceGroups.length > 3 && ( - +{library.sourceFriends.length - 3} + +{library.sourceGroups.length - 3} )}
- ) : library.sourceType === 2 && library.sourceGroups?.length > 0 ? ( -
-
- {(library.groupsData || []).slice(0, 3).map((group) => ( - {group.name - ))} - {library.sourceGroups.length > 3 && ( - - +{library.sourceGroups.length - 3} - - )} -
-
- ) : ( -
- )} -
-
创建人:{library.creator}
-
内容数量:{library.itemCount}
-
更新时间:{new Date(library.updateTime).toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - })}
+
+ ) : ( +
+ )}
+
创建人:{library.creator}
+
内容数量:{library.itemCount}
+
更新时间:{new Date(library.updateTime).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })}
- - - - - - handleEdit(library.id)}> - - 编辑 - - handleDelete(library.id)}> - - 删除 - - handleViewMaterials(library.id)}> - - 查看素材 - - -
-
- )) - )} -
+ navigate(`/content/${library.id}`)} + onEdit={() => handleEdit(library.id)} + onDelete={() => handleDelete(library.id)} + onViewMaterials={() => handleViewMaterials(library.id)} + /> +
+ + )) + )}
+
); } \ No newline at end of file diff --git a/nkebao/src/pages/content/NewContent.tsx b/nkebao/src/pages/content/NewContent.tsx new file mode 100644 index 00000000..540af0a1 --- /dev/null +++ b/nkebao/src/pages/content/NewContent.tsx @@ -0,0 +1,202 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import Layout from '@/components/Layout'; +import UnifiedHeader from '@/components/UnifiedHeader'; +import BottomNav from '@/components/BottomNav'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Switch } from '@/components/ui/switch'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { Card } from '@/components/ui/card'; +import { Collapse, CollapsePanel } from 'tdesign-mobile-react'; +import { toast } from '@/components/ui/toast'; +// TODO: 引入微信好友/群组选择器、日期选择器等组件 + +interface WechatFriend { id: string; nickname: string; avatar: string; } +interface WechatGroup { id: string; name: string; avatar: string; } + +interface ContentLibraryForm { + name: string; + sourceType: 'friends' | 'groups'; + keywordsInclude: string; + keywordsExclude: string; + startDate: string; + endDate: string; + selectedFriends: WechatFriend[]; + selectedGroups: WechatGroup[]; + useAI: boolean; + aiPrompt: string; + enabled: boolean; +} + +export default function NewContentLibraryPage() { + const navigate = useNavigate(); + const [form, setForm] = useState({ + name: '', + sourceType: 'friends', + keywordsInclude: '', + keywordsExclude: '', + startDate: '', + endDate: '', + selectedFriends: [], + selectedGroups: [], + useAI: false, + aiPrompt: '', + enabled: true, + }); + const [isFriendSelectorOpen, setIsFriendSelectorOpen] = useState(false); + const [isGroupSelectorOpen, setIsGroupSelectorOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + // TODO: 选择器、日期选择器等逻辑 + + const handleSave = async () => { + setIsSubmitting(true); + try { + await new Promise((resolve) => setTimeout(resolve, 500)); + toast({ title: '创建成功', description: '内容库已保存' }); + navigate('/content'); + } catch (error) { + toast({ title: '创建失败', description: '保存内容库失败', variant: 'destructive' }); + } finally { + setIsSubmitting(false); + } + }; + + return ( + navigate(-1)} />} + footer={} + > +
+
+ +
+
+ + setForm(f => ({ ...f, name: e.target.value }))} + placeholder="请输入内容库名称" + required + /> +
+
+ + setForm(f => ({ ...f, sourceType: val as 'friends' | 'groups' }))}> + + 选择微信好友 + 选择聊天群 + + + + {form.selectedFriends.length > 0 && ( +
+ {form.selectedFriends.map(friend => ( +
+ {friend.nickname} +
+ ))} +
+ )} +
+ + + {form.selectedGroups.length > 0 && ( +
+ {form.selectedGroups.map(group => ( +
+ {group.name} +
+ ))} +
+ )} +
+
+
+ + +
+
+ +