选择的处理下

This commit is contained in:
笔记本里的永平
2025-07-17 09:59:54 +08:00
parent 42ba2590f7
commit 43e873f83a
3 changed files with 341 additions and 166 deletions

View File

@@ -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>
);
}
}