选择的处理下
This commit is contained in:
@@ -41,18 +41,11 @@ export default function DeviceSelection({
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 当弹窗打开时获取设备列表
|
||||
useEffect(() => {
|
||||
if (dialogOpen) {
|
||||
fetchDevices();
|
||||
}
|
||||
}, [dialogOpen]);
|
||||
|
||||
// 获取设备列表
|
||||
const fetchDevices = async () => {
|
||||
// 获取设备列表,支持keyword
|
||||
const fetchDevices = async (keyword: string = "") => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetchDeviceList(1, 100);
|
||||
const res = await fetchDeviceList(1, 100, keyword.trim() || undefined);
|
||||
if (res && res.data && Array.isArray(res.data.list)) {
|
||||
setDevices(
|
||||
res.data.list.map((d) => ({
|
||||
@@ -71,19 +64,29 @@ export default function DeviceSelection({
|
||||
}
|
||||
};
|
||||
|
||||
// 过滤设备
|
||||
const filteredDevices = devices.filter((device) => {
|
||||
const matchesSearch =
|
||||
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
device.imei.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
device.wechatId.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
// 打开弹窗时获取设备列表
|
||||
const openDialog = () => {
|
||||
setSearchQuery("");
|
||||
setDialogOpen(true);
|
||||
fetchDevices("");
|
||||
};
|
||||
|
||||
// 搜索防抖
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) return;
|
||||
const timer = setTimeout(() => {
|
||||
fetchDevices(searchQuery);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, dialogOpen]);
|
||||
|
||||
// 过滤设备(只保留状态过滤)
|
||||
const filteredDevices = devices.filter((device) => {
|
||||
const matchesStatus =
|
||||
statusFilter === "all" ||
|
||||
(statusFilter === "online" && device.status === "online") ||
|
||||
(statusFilter === "offline" && device.status === "offline");
|
||||
|
||||
return matchesSearch && matchesStatus;
|
||||
return matchesStatus;
|
||||
});
|
||||
|
||||
// 处理设备选择
|
||||
@@ -110,7 +113,7 @@ export default function DeviceSelection({
|
||||
placeholder={placeholder}
|
||||
className="pl-10 h-14 rounded-xl border-gray-200 text-base"
|
||||
readOnly
|
||||
onClick={() => setDialogOpen(true)}
|
||||
onClick={openDialog}
|
||||
value={getDisplayText()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -42,62 +42,74 @@ export function DeviceSelectionDialog({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [devices, setDevices] = useState<Device[]>([]);
|
||||
|
||||
// 获取设备列表
|
||||
const fetchDevices = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetchDeviceList(1, 100, searchQuery);
|
||||
if (response.code === 200 && response.data) {
|
||||
// 转换服务端数据格式为组件需要的格式
|
||||
const convertedDevices: Device[] = response.data.list.map(
|
||||
(serverDevice: ServerDevice) => ({
|
||||
id: serverDevice.id.toString(),
|
||||
name: serverDevice.memo || `设备 ${serverDevice.id}`,
|
||||
imei: serverDevice.imei,
|
||||
wxid: serverDevice.wechatId || "",
|
||||
status: serverDevice.alive === 1 ? "online" : "offline",
|
||||
usedInPlans: 0, // 这个字段需要从其他API获取
|
||||
nickname: serverDevice.nickname || "",
|
||||
})
|
||||
// 获取设备列表,支持keyword
|
||||
const fetchDevices = useCallback(
|
||||
async (keyword: string = "") => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetchDeviceList(
|
||||
1,
|
||||
100,
|
||||
keyword.trim() || undefined
|
||||
);
|
||||
setDevices(convertedDevices);
|
||||
} else {
|
||||
if (response.code === 200 && response.data) {
|
||||
// 转换服务端数据格式为组件需要的格式
|
||||
const convertedDevices: Device[] = response.data.list.map(
|
||||
(serverDevice: ServerDevice) => ({
|
||||
id: serverDevice.id.toString(),
|
||||
name: serverDevice.memo || `设备 ${serverDevice.id}`,
|
||||
imei: serverDevice.imei,
|
||||
wxid: serverDevice.wechatId || "",
|
||||
status: serverDevice.alive === 1 ? "online" : "offline",
|
||||
usedInPlans: 0, // 这个字段需要从其他API获取
|
||||
nickname: serverDevice.nickname || "",
|
||||
})
|
||||
);
|
||||
setDevices(convertedDevices);
|
||||
} else {
|
||||
toast({
|
||||
title: "获取设备列表失败",
|
||||
description: response.msg,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取设备列表失败:", error);
|
||||
toast({
|
||||
title: "获取设备列表失败",
|
||||
description: response.msg,
|
||||
description: "请检查网络连接",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取设备列表失败:", error);
|
||||
toast({
|
||||
title: "获取设备列表失败",
|
||||
description: "请检查网络连接",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [searchQuery, toast]);
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
// 打开弹窗时获取设备列表
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchDevices();
|
||||
fetchDevices("");
|
||||
}
|
||||
}, [open, searchQuery, fetchDevices]);
|
||||
}, [open, fetchDevices]);
|
||||
|
||||
// 搜索防抖
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const timer = setTimeout(() => {
|
||||
fetchDevices(searchQuery);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery, open, fetchDevices]);
|
||||
|
||||
// 过滤设备(只保留状态过滤)
|
||||
const filteredDevices = devices.filter((device) => {
|
||||
const matchesSearch =
|
||||
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
device.imei.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
device.wxid.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesStatus =
|
||||
statusFilter === "all" ||
|
||||
(statusFilter === "online" && device.status === "online") ||
|
||||
(statusFilter === "offline" && device.status === "offline");
|
||||
|
||||
return matchesSearch && matchesStatus;
|
||||
return matchesStatus;
|
||||
});
|
||||
|
||||
const handleDeviceSelect = (deviceId: string) => {
|
||||
@@ -137,7 +149,7 @@ export function DeviceSelectionDialog({
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={fetchDevices}
|
||||
onClick={() => fetchDevices(searchQuery)}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import Layout from '@/components/Layout';
|
||||
import UnifiedHeader from '@/components/UnifiedHeader';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { Card } from '@/components/ui/card';
|
||||
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 { get, post } from '@/api/request';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Layout from "@/components/Layout";
|
||||
import UnifiedHeader from "@/components/UnifiedHeader";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { Card } from "@/components/ui/card";
|
||||
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 { get, post } from "@/api/request";
|
||||
// TODO: 引入微信好友/群组选择器、日期选择器等组件
|
||||
|
||||
interface WechatFriend { id: string; nickname: string; avatar: string; }
|
||||
interface WechatGroup { id: string; name: string; avatar: string; }
|
||||
interface WechatFriend {
|
||||
id: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
}
|
||||
interface WechatGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
interface ContentLibraryForm {
|
||||
name: string;
|
||||
sourceType: 'friends' | 'groups';
|
||||
sourceType: "friends" | "groups";
|
||||
keywordsInclude: string;
|
||||
keywordsExclude: string;
|
||||
startDate: string;
|
||||
@@ -36,19 +44,21 @@ export default function NewContentLibraryPage() {
|
||||
const { id } = useParams();
|
||||
const isEdit = !!id;
|
||||
const [form, setForm] = useState<ContentLibraryForm>({
|
||||
name: '',
|
||||
sourceType: 'friends',
|
||||
keywordsInclude: '',
|
||||
keywordsExclude: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
name: "",
|
||||
sourceType: "friends",
|
||||
keywordsInclude: "",
|
||||
keywordsExclude: "",
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
selectedFriends: [],
|
||||
selectedGroups: [],
|
||||
useAI: false,
|
||||
aiPrompt: '',
|
||||
aiPrompt: "",
|
||||
enabled: true,
|
||||
});
|
||||
const [selectedFriendObjs, setSelectedFriendObjs] = useState<WechatFriend[]>([]);
|
||||
const [selectedFriendObjs, setSelectedFriendObjs] = useState<WechatFriend[]>(
|
||||
[]
|
||||
);
|
||||
const [selectedGroupObjs, setSelectedGroupObjs] = useState<WechatGroup[]>([]);
|
||||
const [isFriendSelectorOpen, setIsFriendSelectorOpen] = useState(false);
|
||||
const [isGroupSelectorOpen, setIsGroupSelectorOpen] = useState(false);
|
||||
@@ -62,31 +72,66 @@ export default function NewContentLibraryPage() {
|
||||
const data = res.data;
|
||||
// 时间戳转YYYY-MM-DD
|
||||
const formatDate = (val: number) => {
|
||||
if (!val || val === 0 || typeof val !== 'number' || isNaN(val) || val < 1000000000) return '';
|
||||
if (
|
||||
!val ||
|
||||
val === 0 ||
|
||||
typeof val !== "number" ||
|
||||
isNaN(val) ||
|
||||
val < 1000000000
|
||||
)
|
||||
return "";
|
||||
try {
|
||||
const d = new Date(val * 1000);
|
||||
if (isNaN(d.getTime())) return '';
|
||||
if (isNaN(d.getTime())) return "";
|
||||
return d.toISOString().slice(0, 10);
|
||||
} catch {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
};
|
||||
setForm(f => ({
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
name: data.name || '',
|
||||
sourceType: data.sourceType === 1 ? 'friends' : 'groups',
|
||||
keywordsInclude: (data.keywordInclude || []).join(','),
|
||||
keywordsExclude: (data.keywordExclude || []).join(','),
|
||||
name: data.name || "",
|
||||
sourceType: data.sourceType === 1 ? "friends" : "groups",
|
||||
keywordsInclude: (data.keywordInclude || []).join(","),
|
||||
keywordsExclude: (data.keywordExclude || []).join(","),
|
||||
startDate: formatDate(data.timeStart),
|
||||
endDate: formatDate(data.timeEnd),
|
||||
selectedFriends: (data.selectedFriends || data.sourceFriends || []).map((fid: number | string) => ({ id: String(fid), nickname: String(fid), avatar: '' })),
|
||||
selectedGroups: (data.sourceGroups || []).map((gid: number | string) => ({ id: String(gid), name: String(gid), avatar: '' })),
|
||||
selectedFriends: (
|
||||
data.selectedFriends ||
|
||||
data.sourceFriends ||
|
||||
[]
|
||||
).map((fid: number | string) => ({
|
||||
id: String(fid),
|
||||
nickname: String(fid),
|
||||
avatar: "",
|
||||
})),
|
||||
selectedGroups: (data.sourceGroups || []).map(
|
||||
(gid: number | string) => ({
|
||||
id: String(gid),
|
||||
name: String(gid),
|
||||
avatar: "",
|
||||
})
|
||||
),
|
||||
useAI: data.aiEnabled === 1,
|
||||
aiPrompt: data.aiPrompt || '',
|
||||
aiPrompt: data.aiPrompt || "",
|
||||
enabled: data.status === 1,
|
||||
}));
|
||||
setSelectedFriendObjs((data.selectedFriends || data.sourceFriends || []).map((fid: number | string) => ({ id: String(fid), nickname: String(fid), avatar: '' })));
|
||||
setSelectedGroupObjs((data.sourceGroups || []).map((gid: number | string) => ({ id: String(gid), name: String(gid), avatar: '' })));
|
||||
setSelectedFriendObjs(
|
||||
(data.selectedFriends || data.sourceFriends || []).map(
|
||||
(fid: number | string) => ({
|
||||
id: String(fid),
|
||||
nickname: String(fid),
|
||||
avatar: "",
|
||||
})
|
||||
)
|
||||
);
|
||||
setSelectedGroupObjs(
|
||||
(data.sourceGroups || []).map((gid: number | string) => ({
|
||||
id: String(gid),
|
||||
name: String(gid),
|
||||
avatar: "",
|
||||
}))
|
||||
);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -100,27 +145,44 @@ export default function NewContentLibraryPage() {
|
||||
const payload = {
|
||||
id: isEdit ? id : undefined,
|
||||
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)),
|
||||
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) : [],
|
||||
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
|
||||
startTime: form.startDate || "",
|
||||
endTime: form.endDate || "",
|
||||
status: form.enabled ? 1 : 0,
|
||||
};
|
||||
if (isEdit) {
|
||||
await post('/v1/content/library/update', payload);
|
||||
await post("/v1/content/library/update", payload);
|
||||
} else {
|
||||
await post('/v1/content/library/create', payload);
|
||||
await post("/v1/content/library/create", payload);
|
||||
}
|
||||
toast({ title: isEdit ? '保存成功' : '创建成功', description: '内容库已保存' });
|
||||
navigate('/content');
|
||||
toast({
|
||||
title: isEdit ? "保存成功" : "创建成功",
|
||||
description: "内容库已保存",
|
||||
});
|
||||
navigate("/content");
|
||||
} catch (error) {
|
||||
toast({ title: isEdit ? '保存失败' : '创建失败', description: '保存内容库失败', variant: 'destructive' });
|
||||
toast({
|
||||
title: isEdit ? "保存失败" : "创建失败",
|
||||
description: "保存内容库失败",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -128,66 +190,114 @@ export default function NewContentLibraryPage() {
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={<UnifiedHeader title={isEdit ? "编辑内容库" : "新建内容库"} showBack onBack={() => navigate(-1)} />}
|
||||
header={
|
||||
<UnifiedHeader
|
||||
title={isEdit ? "编辑内容库" : "新建内容库"}
|
||||
showBack
|
||||
onBack={() => navigate(-1)}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<div className="p-4">
|
||||
<Button theme="primary" block onClick={handleSave} disabled={isSubmitting || !form.name} >
|
||||
{isSubmitting ? (isEdit ? '保存中...' : '创建中...') : (isEdit ? '保存' : '创建内容库')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Button
|
||||
theme="primary"
|
||||
block
|
||||
onClick={handleSave}
|
||||
disabled={isSubmitting || !form.name}
|
||||
>
|
||||
{isSubmitting
|
||||
? isEdit
|
||||
? "保存中..."
|
||||
: "创建中..."
|
||||
: isEdit
|
||||
? "保存"
|
||||
: "创建内容库"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex-1 bg-gray-50 min-h-screen pb-16">
|
||||
<div className="flex-1 bg-gray-50 ">
|
||||
<div className="p-4 space-y-4 max-w-lg mx-auto">
|
||||
<Card className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">内容库名称 <span className="text-red-500">*</span></label>
|
||||
<label className="block font-medium mb-1">
|
||||
内容库名称 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={e => setForm(f => ({ ...f, name: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({ ...f, name: e.target.value }))
|
||||
}
|
||||
placeholder="请输入内容库名称"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">数据来源配置</label>
|
||||
<Tabs value={form.sourceType} onValueChange={val => setForm(f => ({ ...f, sourceType: val as 'friends' | 'groups' }))}>
|
||||
<Tabs
|
||||
value={form.sourceType}
|
||||
onValueChange={(val) =>
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
sourceType: val as "friends" | "groups",
|
||||
}))
|
||||
}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="friends">选择微信好友</TabsTrigger>
|
||||
<TabsTrigger value="groups">选择聊天群</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="friends">
|
||||
<FriendSelection
|
||||
selectedFriends={form.selectedFriends.map(f => f.id)}
|
||||
onSelect={ids => setForm(f => ({
|
||||
...f,
|
||||
selectedFriends: ids.map(id => ({ id, nickname: id, avatar: '' }))
|
||||
}))}
|
||||
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">
|
||||
{selectedFriendObjs.map(friend => (
|
||||
<div key={friend.id} className="flex items-center justify-between bg-gray-100 p-2 rounded-md">
|
||||
{selectedFriendObjs.map((friend) => (
|
||||
<div
|
||||
key={friend.id}
|
||||
className="flex items-center justify-between bg-gray-100 p-2 rounded-md"
|
||||
>
|
||||
<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" />
|
||||
<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>
|
||||
<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 => ({
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
selectedFriends: f.selectedFriends.filter(frd => frd.id !== friend.id)
|
||||
selectedFriends: f.selectedFriends.filter(
|
||||
(frd) => frd.id !== friend.id
|
||||
),
|
||||
}));
|
||||
setSelectedFriendObjs(objs => objs.filter(frd => frd.id !== friend.id));
|
||||
setSelectedFriendObjs((objs) =>
|
||||
objs.filter((frd) => frd.id !== friend.id)
|
||||
);
|
||||
}}
|
||||
title="移除"
|
||||
>
|
||||
@@ -200,37 +310,54 @@ export default function NewContentLibraryPage() {
|
||||
</TabsContent>
|
||||
<TabsContent value="groups">
|
||||
<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: '' };
|
||||
})
|
||||
}))}
|
||||
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">
|
||||
{selectedGroupObjs.map(group => (
|
||||
<div key={group.id} className="flex items-center justify-between bg-gray-100 p-2 rounded-md">
|
||||
{selectedGroupObjs.map((group) => (
|
||||
<div
|
||||
key={group.id}
|
||||
className="flex items-center justify-between bg-gray-100 p-2 rounded-md"
|
||||
>
|
||||
<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" />
|
||||
<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>
|
||||
<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 => ({
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
selectedGroups: f.selectedGroups.filter(grp => grp.id !== group.id)
|
||||
selectedGroups: f.selectedGroups.filter(
|
||||
(grp) => grp.id !== group.id
|
||||
),
|
||||
}));
|
||||
setSelectedGroupObjs(objs => objs.filter(grp => grp.id !== group.id));
|
||||
setSelectedGroupObjs((objs) =>
|
||||
objs.filter((grp) => grp.id !== group.id)
|
||||
);
|
||||
}}
|
||||
title="移除"
|
||||
>
|
||||
@@ -247,18 +374,32 @@ export default function NewContentLibraryPage() {
|
||||
<CollapsePanel header="关键字设置" value="keywords">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">关键字匹配</label>
|
||||
<label className="block font-medium mb-1">
|
||||
关键字匹配
|
||||
</label>
|
||||
<Textarea
|
||||
value={form.keywordsInclude}
|
||||
onChange={e => setForm(f => ({ ...f, keywordsInclude: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
keywordsInclude: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="如果设置了关键字,系统只会采集含有关键字的内容。多个关键字,用半角的','隔开。"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">关键字排除</label>
|
||||
<label className="block font-medium mb-1">
|
||||
关键字排除
|
||||
</label>
|
||||
<Textarea
|
||||
value={form.keywordsExclude}
|
||||
onChange={e => setForm(f => ({ ...f, keywordsExclude: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
keywordsExclude: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="排除含有这些关键字的内容。多个关键字,用半角的','隔开。"
|
||||
/>
|
||||
</div>
|
||||
@@ -269,17 +410,26 @@ export default function NewContentLibraryPage() {
|
||||
<div>
|
||||
<label className="block font-medium">是否启用AI</label>
|
||||
</div>
|
||||
<div className='w-10'>
|
||||
<Switch checked={form.useAI} onCheckedChange={checked => setForm(f => ({ ...f, useAI: checked }))} />
|
||||
<div className="w-10">
|
||||
<Switch
|
||||
checked={form.useAI}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm((f) => ({ ...f, useAI: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1 ">当启用AI之后,该内容库下的所有内容,都会通过AI重新生成内容。</p>
|
||||
<p className="text-sm text-gray-500 mt-1 ">
|
||||
当启用AI之后,该内容库下的所有内容,都会通过AI重新生成内容。
|
||||
</p>
|
||||
{form.useAI && (
|
||||
<div>
|
||||
<label className="block font-medium mb-1">AI 提示词</label>
|
||||
<Textarea
|
||||
value={form.aiPrompt}
|
||||
onChange={e => setForm(f => ({ ...f, aiPrompt: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({ ...f, aiPrompt: e.target.value }))
|
||||
}
|
||||
placeholder="请输入 AI 提示词"
|
||||
/>
|
||||
</div>
|
||||
@@ -287,31 +437,41 @@ export default function NewContentLibraryPage() {
|
||||
<div>
|
||||
<label className="block font-medium mb-2">时间限制</label>
|
||||
{/* TODO: 替换为TDesign日期范围选择器 */}
|
||||
<div className='flex mb-2' style={{ justifyContent: 'space-between' }}>
|
||||
<label className='text-sm w-20 '>开始时间</label>
|
||||
<div
|
||||
className="flex mb-2"
|
||||
style={{ justifyContent: "space-between" }}
|
||||
>
|
||||
<label className="text-sm w-20 ">开始时间</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={form.startDate}
|
||||
onChange={e => setForm(f => ({ ...f, startDate: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({ ...f, startDate: e.target.value }))
|
||||
}
|
||||
className="inline-block w-1/2 "
|
||||
/>
|
||||
</div>
|
||||
<div className='flex '>
|
||||
<label className='text-sm w-20' >结束时间</label>
|
||||
<div className="flex ">
|
||||
<label className="text-sm w-20">结束时间</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={form.endDate}
|
||||
onChange={e => setForm(f => ({ ...f, endDate: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setForm((f) => ({ ...f, endDate: e.target.value }))
|
||||
}
|
||||
className="inline-block w-1/2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="block font-medium mb-1">是否启用</label>
|
||||
<Switch checked={form.enabled} onCheckedChange={checked => setForm(f => ({ ...f, enabled: checked }))} />
|
||||
<Switch
|
||||
checked={form.enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm((f) => ({ ...f, enabled: checked }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -319,4 +479,4 @@ export default function NewContentLibraryPage() {
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user