feat: 本次提交更新内容如下
新建内容库构建完成
This commit is contained in:
@@ -34,29 +34,35 @@ interface FriendsResponse {
|
||||
}
|
||||
|
||||
// 获取好友列表API函数
|
||||
const fetchFriendsList = async (page: number = 1, limit: number = 20, deviceIds: string[]): Promise<FriendsResponse> => {
|
||||
if (deviceIds.length === 0) {
|
||||
const fetchFriendsList = async (params: {
|
||||
page: number;
|
||||
limit: number;
|
||||
deviceIds?: string[];
|
||||
}): Promise<FriendsResponse> => {
|
||||
if (params.deviceIds && params.deviceIds.length === 0) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'success',
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page,
|
||||
limit
|
||||
page: params.page,
|
||||
limit: params.limit
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const deviceIdsParam = deviceIds.join(',');
|
||||
return get<FriendsResponse>(`/v1/friend?page=${page}&limit=${limit}&deviceIds=${deviceIdsParam}`);
|
||||
const deviceIdsParam = params?.deviceIds?.join(',') || ''
|
||||
return get<FriendsResponse>(`/v1/friend?page=${ params.page}&limit=${params.limit}&deviceIds=${deviceIdsParam}`);
|
||||
};
|
||||
|
||||
// 组件属性接口
|
||||
interface FriendSelectionProps {
|
||||
selectedFriends: string[];
|
||||
onSelect: (friends: string[]) => void;
|
||||
deviceIds: string[];
|
||||
onSelectDetail?: (friends: WechatFriend[]) => void; // 新增
|
||||
deviceIds?: string[];
|
||||
enableDeviceFilter?: boolean; // 新增开关,默认true
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}
|
||||
@@ -64,7 +70,9 @@ interface FriendSelectionProps {
|
||||
export default function FriendSelection({
|
||||
selectedFriends,
|
||||
onSelect,
|
||||
deviceIds,
|
||||
onSelectDetail,
|
||||
deviceIds = [],
|
||||
enableDeviceFilter = true,
|
||||
placeholder = "选择微信好友",
|
||||
className = ""
|
||||
}: FriendSelectionProps) {
|
||||
@@ -76,27 +84,38 @@ export default function FriendSelection({
|
||||
const [totalFriends, setTotalFriends] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 当弹窗打开时获取好友列表
|
||||
// 打开弹窗并请求第一页好友
|
||||
const openDialog = () => {
|
||||
setCurrentPage(1);
|
||||
setDialogOpen(true);
|
||||
fetchFriends(1);
|
||||
};
|
||||
|
||||
// 当页码变化时,拉取对应页数据(弹窗已打开时)
|
||||
useEffect(() => {
|
||||
if (dialogOpen && deviceIds.length > 0) {
|
||||
if (dialogOpen && currentPage !== 1) {
|
||||
fetchFriends(currentPage);
|
||||
}
|
||||
}, [dialogOpen, currentPage, deviceIds]);
|
||||
|
||||
// 当设备ID变化时,重置页码
|
||||
useEffect(() => {
|
||||
if (deviceIds.length > 0) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [deviceIds]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
// 获取好友列表API
|
||||
const fetchFriends = async (page: number) => {
|
||||
if (deviceIds.length === 0) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchFriendsList(page, 20, deviceIds);
|
||||
let res;
|
||||
if (enableDeviceFilter) {
|
||||
if (deviceIds.length === 0) {
|
||||
setFriends([]);
|
||||
setTotalFriends(0);
|
||||
setTotalPages(1);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
res = await fetchFriendsList({ page, limit: 20, deviceIds: deviceIds });
|
||||
} else {
|
||||
res = await fetchFriendsList({ page, limit: 20 });
|
||||
}
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
setFriends(res.data.list.map((friend) => ({
|
||||
@@ -106,7 +125,6 @@ export default function FriendSelection({
|
||||
avatar: friend.avatar || '',
|
||||
customer: friend.customer || '',
|
||||
})));
|
||||
|
||||
setTotalFriends(res.data.total || 0);
|
||||
setTotalPages(Math.ceil((res.data.total || 0) / 20));
|
||||
}
|
||||
@@ -125,10 +143,16 @@ export default function FriendSelection({
|
||||
|
||||
// 处理好友选择
|
||||
const handleFriendToggle = (friendId: string) => {
|
||||
let newIds: string[];
|
||||
if (selectedFriends.includes(friendId)) {
|
||||
onSelect(selectedFriends.filter(id => id !== friendId));
|
||||
newIds = selectedFriends.filter(id => id !== friendId);
|
||||
} else {
|
||||
onSelect([...selectedFriends, friendId]);
|
||||
newIds = [...selectedFriends, friendId];
|
||||
}
|
||||
onSelect(newIds);
|
||||
if (onSelectDetail) {
|
||||
const selectedObjs = friends.filter(f => newIds.includes(f.id));
|
||||
onSelectDetail(selectedObjs);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +179,7 @@ export default function FriendSelection({
|
||||
placeholder={placeholder}
|
||||
className="pl-10 h-12 rounded-xl border-gray-200 text-base"
|
||||
readOnly
|
||||
onClick={() => setDialogOpen(true)}
|
||||
onClick={openDialog}
|
||||
value={getDisplayText()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
264
nkebao/src/components/GroupSelection.tsx
Normal file
264
nkebao/src/components/GroupSelection.tsx
Normal file
@@ -0,0 +1,264 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
||||
import { get } from '@/api/request';
|
||||
|
||||
// 群组接口类型
|
||||
interface WechatGroup {
|
||||
id: string;
|
||||
chatroomId: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
ownerWechatId: string;
|
||||
ownerNickname: string;
|
||||
ownerAvatar: string;
|
||||
}
|
||||
|
||||
interface GroupsResponse {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: {
|
||||
list: Array<{
|
||||
id: number;
|
||||
chatroomId: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
ownerWechatId?: string;
|
||||
ownerNickname?: string;
|
||||
ownerAvatar?: string;
|
||||
}>;
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
|
||||
const fetchGroupsList = async (params: { page: number; limit: number; }): Promise<GroupsResponse> => {
|
||||
return get<GroupsResponse>(`/v1/chatroom?page=${params.page}&limit=${params.limit}`);
|
||||
};
|
||||
|
||||
interface GroupSelectionProps {
|
||||
selectedGroups: string[];
|
||||
onSelect: (groups: string[]) => void;
|
||||
onSelectDetail?: (groups: WechatGroup[]) => void; // 新增
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function GroupSelection({
|
||||
selectedGroups,
|
||||
onSelect,
|
||||
onSelectDetail,
|
||||
placeholder = '选择群聊',
|
||||
className = ''
|
||||
}: GroupSelectionProps) {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [groups, setGroups] = useState<WechatGroup[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalGroups, setTotalGroups] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 打开弹窗并请求第一页群组
|
||||
const openDialog = () => {
|
||||
setCurrentPage(1);
|
||||
setDialogOpen(true);
|
||||
fetchGroups(1);
|
||||
};
|
||||
|
||||
// 当页码变化时,拉取对应页数据(弹窗已打开时)
|
||||
useEffect(() => {
|
||||
if (dialogOpen && currentPage !== 1) {
|
||||
fetchGroups(currentPage);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
// 获取群组列表API
|
||||
const fetchGroups = async (page: number) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchGroupsList({ page, limit: 20 });
|
||||
if (res && res.code === 200 && res.data) {
|
||||
setGroups(res.data.list.map((group) => ({
|
||||
id: group.id?.toString() || '',
|
||||
chatroomId: group.chatroomId || '',
|
||||
name: group.name || '',
|
||||
avatar: group.avatar || '',
|
||||
ownerWechatId: group.ownerWechatId || '',
|
||||
ownerNickname: group.ownerNickname || '',
|
||||
ownerAvatar: group.ownerAvatar || '',
|
||||
})));
|
||||
setTotalGroups(res.data.total || 0);
|
||||
setTotalPages(Math.ceil((res.data.total || 0) / 20));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取群组列表失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 过滤群组
|
||||
const filteredGroups = groups.filter(group =>
|
||||
group.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
group.chatroomId.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
// 处理群组选择
|
||||
const handleGroupToggle = (groupId: string) => {
|
||||
let newIds: string[];
|
||||
if (selectedGroups.includes(groupId)) {
|
||||
newIds = selectedGroups.filter(id => id !== groupId);
|
||||
} else {
|
||||
newIds = [...selectedGroups, groupId];
|
||||
}
|
||||
onSelect(newIds);
|
||||
if (onSelectDetail) {
|
||||
const selectedObjs = groups.filter(g => newIds.includes(g.id));
|
||||
onSelectDetail(selectedObjs);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取显示文本
|
||||
const getDisplayText = () => {
|
||||
if (selectedGroups.length === 0) return '';
|
||||
return `已选择 ${selectedGroups.length} 个群聊`;
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 输入框 */}
|
||||
<div className={`relative ${className}`}>
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</span>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
className="pl-10 h-12 rounded-xl border-gray-200 text-base"
|
||||
readOnly
|
||||
onClick={openDialog}
|
||||
value={getDisplayText()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 群组选择弹窗 */}
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="max-w-xl max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden">
|
||||
<div className="p-6">
|
||||
<DialogTitle className="text-center text-xl font-medium mb-6">选择群聊</DialogTitle>
|
||||
<div className="relative mb-4">
|
||||
<Input
|
||||
placeholder="搜索群聊"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 py-2 rounded-full border-gray-200"
|
||||
/>
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 h-6 w-6 rounded-full"
|
||||
onClick={() => setSearchQuery('')}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-gray-500">加载中...</div>
|
||||
</div>
|
||||
) : filteredGroups.length > 0 ? (
|
||||
<div className="divide-y">
|
||||
{filteredGroups.map((group) => (
|
||||
<label
|
||||
key={group.id}
|
||||
className="flex items-center px-6 py-4 hover:bg-gray-50 cursor-pointer"
|
||||
onClick={() => handleGroupToggle(group.id)}
|
||||
>
|
||||
<div className="mr-3 flex items-center justify-center">
|
||||
<div className={`w-5 h-5 rounded-full border ${selectedGroups.includes(group.id) ? 'border-blue-600' : 'border-gray-300'} flex items-center justify-center`}>
|
||||
{selectedGroups.includes(group.id) && (
|
||||
<div className="w-3 h-3 rounded-full bg-blue-600"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3 flex-1">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-blue-400 to-purple-500 flex items-center justify-center text-white text-sm font-medium overflow-hidden">
|
||||
{group.avatar ? (
|
||||
<img src={group.avatar} alt={group.name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
group.name.charAt(0)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{group.name}</div>
|
||||
<div className="text-sm text-gray-500">群ID: {group.chatroomId}</div>
|
||||
{group.ownerNickname && (
|
||||
<div className="text-sm text-gray-400">群主: {group.ownerNickname}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-gray-500">没有找到群聊</div>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
<div className="border-t p-4 flex items-center justify-between bg-white">
|
||||
<div className="text-sm text-gray-500">
|
||||
总计 {totalGroups} 个群聊
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
||||
disabled={currentPage === 1 || loading}
|
||||
className="px-2 py-0 h-8 min-w-0"
|
||||
>
|
||||
<
|
||||
</Button>
|
||||
<span className="text-sm">{currentPage} / {totalPages}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
|
||||
disabled={currentPage === totalPages || loading}
|
||||
className="px-2 py-0 h-8 min-w-0"
|
||||
>
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t p-4 flex items-center justify-between bg-white">
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)} className="px-6 rounded-full border-gray-300">
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} className="px-6 bg-blue-600 hover:bg-blue-700 rounded-full">
|
||||
确定 ({selectedGroups.length})
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,15 +2,16 @@ 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 { Collapse, CollapsePanel ,Button} from 'tdesign-mobile-react';
|
||||
import { toast } from '@/components/ui/toast';
|
||||
import FriendSelection from '@/components/FriendSelection';
|
||||
import GroupSelection from '@/components/GroupSelection';
|
||||
import { post } from '@/api/request';
|
||||
// TODO: 引入微信好友/群组选择器、日期选择器等组件
|
||||
|
||||
interface WechatFriend { id: string; nickname: string; avatar: string; }
|
||||
@@ -45,6 +46,8 @@ export default function NewContentLibraryPage() {
|
||||
aiPrompt: '',
|
||||
enabled: true,
|
||||
});
|
||||
const [selectedFriendObjs, setSelectedFriendObjs] = useState<WechatFriend[]>([]);
|
||||
const [selectedGroupObjs, setSelectedGroupObjs] = useState<WechatGroup[]>([]);
|
||||
const [isFriendSelectorOpen, setIsFriendSelectorOpen] = useState(false);
|
||||
const [isGroupSelectorOpen, setIsGroupSelectorOpen] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
@@ -54,7 +57,22 @@ export default function NewContentLibraryPage() {
|
||||
const handleSave = async () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
// 组装提交参数
|
||||
const payload = {
|
||||
name: form.name,
|
||||
sourceType: form.sourceType === 'friends' ? 1 : 2,
|
||||
friends: form.selectedFriends.map(f => Number(f.id)),
|
||||
groups: form.selectedGroups.map(g => Number(g.id)),
|
||||
groupMembers: {},
|
||||
keywordInclude: form.keywordsInclude ? form.keywordsInclude.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
keywordExclude: form.keywordsExclude ? form.keywordsExclude.split(',').map(s => s.trim()).filter(Boolean) : [],
|
||||
aiPrompt: form.aiPrompt,
|
||||
timeEnabled: form.startDate || form.endDate ? 1 : 0,
|
||||
startTime: form.startDate || '',
|
||||
endTime: form.endDate || '',
|
||||
status: form.enabled ? 1 : 0
|
||||
};
|
||||
await post('/v1/content/library/create', payload);
|
||||
toast({ title: '创建成功', description: '内容库已保存' });
|
||||
navigate('/content');
|
||||
} catch (error) {
|
||||
@@ -67,7 +85,13 @@ export default function NewContentLibraryPage() {
|
||||
return (
|
||||
<Layout
|
||||
header={<UnifiedHeader title="新建内容库" showBack onBack={() => navigate(-1)} />}
|
||||
footer={<BottomNav />}
|
||||
footer={
|
||||
<div className="p-4">
|
||||
<Button theme="primary" block onClick={handleSave} disabled={isSubmitting || !form.name} >
|
||||
{isSubmitting ? '创建中...' : '创建内容库'}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex-1 bg-gray-50 min-h-screen pb-16">
|
||||
<div className="p-4 space-y-4 max-w-lg mx-auto">
|
||||
@@ -90,28 +114,84 @@ export default function NewContentLibraryPage() {
|
||||
<TabsTrigger value="groups">选择聊天群</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="friends">
|
||||
<Button variant="outline" className="w-full" onClick={() => setIsFriendSelectorOpen(true)}>
|
||||
选择微信好友
|
||||
</Button>
|
||||
{form.selectedFriends.length > 0 && (
|
||||
<FriendSelection
|
||||
selectedFriends={form.selectedFriends.map(f => f.id)}
|
||||
onSelect={ids => setForm(f => ({
|
||||
...f,
|
||||
selectedFriends: ids.map(id => ({ id, nickname: id, avatar: '' }))
|
||||
}))}
|
||||
onSelectDetail={setSelectedFriendObjs}
|
||||
enableDeviceFilter={false}
|
||||
placeholder="选择微信好友"
|
||||
/>
|
||||
{selectedFriendObjs.length > 0 && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{form.selectedFriends.map(friend => (
|
||||
{selectedFriendObjs.map(friend => (
|
||||
<div key={friend.id} className="flex items-center justify-between bg-gray-100 p-2 rounded-md">
|
||||
<span>{friend.nickname}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{friend.avatar ? (
|
||||
<img src={friend.avatar} alt={friend.nickname} className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-white text-sm">{friend.nickname?.charAt(0) || '友'}</div>
|
||||
)}
|
||||
<span>{friend.nickname}</span>
|
||||
</div>
|
||||
<button
|
||||
className="text-gray-400 hover:text-red-500 ml-2"
|
||||
onClick={() => {
|
||||
setForm(f => ({
|
||||
...f,
|
||||
selectedFriends: f.selectedFriends.filter(frd => frd.id !== friend.id)
|
||||
}));
|
||||
setSelectedFriendObjs(objs => objs.filter(frd => frd.id !== friend.id));
|
||||
}}
|
||||
title="移除"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="groups">
|
||||
<Button variant="outline" className="w-full" onClick={() => setIsGroupSelectorOpen(true)}>
|
||||
选择聊天群
|
||||
</Button>
|
||||
{form.selectedGroups.length > 0 && (
|
||||
<GroupSelection
|
||||
selectedGroups={form.selectedGroups.map(g => g.id)}
|
||||
onSelect={ids => setForm(f => ({
|
||||
...f,
|
||||
selectedGroups: ids.map(id => {
|
||||
const old = f.selectedGroups.find(g => g.id === id);
|
||||
return old || { id, name: id, avatar: '' };
|
||||
})
|
||||
}))}
|
||||
onSelectDetail={setSelectedGroupObjs}
|
||||
placeholder="选择群聊"
|
||||
/>
|
||||
{selectedGroupObjs.length > 0 && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{form.selectedGroups.map(group => (
|
||||
{selectedGroupObjs.map(group => (
|
||||
<div key={group.id} className="flex items-center justify-between bg-gray-100 p-2 rounded-md">
|
||||
<span>{group.name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{group.avatar ? (
|
||||
<img src={group.avatar} alt={group.name} className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-white text-sm">{group.name?.charAt(0) || '群'}</div>
|
||||
)}
|
||||
<span>{group.name}</span>
|
||||
</div>
|
||||
<button
|
||||
className="text-gray-400 hover:text-red-500 ml-2"
|
||||
onClick={() => {
|
||||
setForm(f => ({
|
||||
...f,
|
||||
selectedGroups: f.selectedGroups.filter(grp => grp.id !== group.id)
|
||||
}));
|
||||
setSelectedGroupObjs(objs => objs.filter(grp => grp.id !== group.id));
|
||||
}}
|
||||
title="移除"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -187,11 +267,7 @@ export default function NewContentLibraryPage() {
|
||||
<label className="block font-medium mb-1">是否启用</label>
|
||||
<Switch checked={form.enabled} onCheckedChange={checked => setForm(f => ({ ...f, enabled: checked }))} />
|
||||
</div>
|
||||
<div className="flex justify-end mt-4">
|
||||
<Button onClick={handleSave} disabled={isSubmitting || !form.name}>
|
||||
{isSubmitting ? '创建中...' : '创建内容库'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -52,6 +52,7 @@ function formatDate(dateString: string) {
|
||||
|
||||
export default function TrafficPoolDetail() {
|
||||
const { id } = useParams();
|
||||
const [activeTab, setActiveTab] = useState<'base' | 'journey' | 'tags'>('base');
|
||||
const user = getUserById(id as string);
|
||||
if (!user) {
|
||||
return <div className="p-8 text-center text-gray-400">未找到该用户</div>;
|
||||
@@ -61,7 +62,6 @@ export default function TrafficPoolDetail() {
|
||||
const device = getDevice(user.deviceId);
|
||||
// RFM分段
|
||||
const rfmSegment = Object.values(RFM_SEGMENTS).find((seg: any) => seg.name === user.rfmScore.segment) as { name: string; color: string } | undefined;
|
||||
const [activeTab, setActiveTab] = useState<'base' | 'journey' | 'tags'>('base');
|
||||
|
||||
return (
|
||||
<Layout
|
||||
|
||||
Reference in New Issue
Block a user