feat: 本次提交更新内容如下
新建内容逻辑完成
This commit is contained in:
@@ -1,116 +1,138 @@
|
|||||||
.form-page {
|
.form-page {
|
||||||
|
background: #f7f8fa;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: #f5f5f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.form-main {
|
||||||
display: flex;
|
max-width: 420px;
|
||||||
justify-content: center;
|
margin: 0 auto;
|
||||||
align-items: center;
|
padding: 16px 0 0 0;
|
||||||
padding: 40px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form-section {
|
||||||
display: flex;
|
margin-bottom: 18px;
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-card {
|
.form-card {
|
||||||
background: white;
|
border-radius: 16px;
|
||||||
border-radius: 8px;
|
box-shadow: 0 4px 24px rgba(0,0,0,0.06);
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
padding: 24px 18px 18px 18px;
|
||||||
border: none;
|
background: #fff;
|
||||||
padding: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.form-label {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
font-size: 16px;
|
||||||
margin-bottom: 16px;
|
color: #222;
|
||||||
padding-bottom: 8px;
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #222;
|
||||||
|
margin-top: 28px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-block {
|
||||||
|
padding: 12px 0 8px 0;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea {
|
.tabs-bar {
|
||||||
border-radius: 6px;
|
.adm-tabs-header {
|
||||||
border: 1px solid #d9d9d9;
|
background: #f7f8fa;
|
||||||
|
border-radius: 8px;
|
||||||
&:focus {
|
margin-bottom: 8px;
|
||||||
border-color: #1677ff;
|
}
|
||||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
|
.adm-tabs-tab {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-settings {
|
.collapse {
|
||||||
|
margin-top: 12px;
|
||||||
|
.adm-collapse-panel-content {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 18px 14px 10px 14px;
|
||||||
|
margin-top: 2px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.form-label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.adm-input {
|
||||||
|
min-height: 42px;
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 7px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-row, .section-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
align-items: center;
|
||||||
margin-top: 16px;
|
gap: 12px;
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-picker {
|
.ai-desc {
|
||||||
width: 100%;
|
color: #888;
|
||||||
border-radius: 6px;
|
font-size: 13px;
|
||||||
|
flex: 1;
|
||||||
&:focus {
|
|
||||||
border-color: #1677ff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.date-row, .section-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 16px;
|
align-items: center;
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.adm-input {
|
||||||
flex: 1;
|
min-height: 44px;
|
||||||
border-radius: 6px;
|
font-size: 15px;
|
||||||
border: 1px solid #d9d9d9;
|
border-radius: 8px;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #1677ff;
|
|
||||||
color: #1677ff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
flex: 1;
|
margin-top: 32px;
|
||||||
border-radius: 6px;
|
height: 48px !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 覆盖 antd-mobile 的默认样式
|
@media (max-width: 600px) {
|
||||||
:global {
|
.form-main {
|
||||||
.adm-form-item {
|
max-width: 100vw;
|
||||||
margin-bottom: 16px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
.form-card {
|
||||||
.adm-form-item-label {
|
border-radius: 0;
|
||||||
font-size: 14px;
|
box-shadow: none;
|
||||||
color: #333;
|
padding: 16px 6px 12px 6px;
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
.section-title {
|
||||||
.adm-input {
|
font-size: 15px;
|
||||||
border-radius: 6px;
|
margin-top: 22px;
|
||||||
border: 1px solid #d9d9d9;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #1677ff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
|
|
||||||
}
|
}
|
||||||
}
|
.submit-btn {
|
||||||
|
height: 44px !important;
|
||||||
.adm-switch {
|
font-size: 15px;
|
||||||
--checked-color: #1677ff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,331 +1,257 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import { Input as AntdInput, Switch } from "antd";
|
||||||
Button,
|
import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile";
|
||||||
Toast,
|
|
||||||
SpinLoading,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Switch,
|
|
||||||
Card,
|
|
||||||
Space,
|
|
||||||
} from "antd-mobile";
|
|
||||||
import { Input as AntdInput, Select, TimePicker } from "antd";
|
|
||||||
import {
|
|
||||||
ArrowLeftOutlined,
|
|
||||||
SaveOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
TeamOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import Layout from "@/components/Layout/Layout";
|
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import {
|
import FriendSelection from "@/components/FriendSelection";
|
||||||
getContentLibraryDetail,
|
import GroupSelection from "@/components/GroupSelection";
|
||||||
createContentLibrary,
|
import Layout from "@/components/Layout/Layout";
|
||||||
updateContentLibrary,
|
|
||||||
} from "./api";
|
|
||||||
import { ContentLibrary, CreateContentLibraryParams } from "./data";
|
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
import request from "@/api/request";
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
const { TextArea } = AntdInput;
|
const { TextArea } = AntdInput;
|
||||||
|
|
||||||
const ContentLibraryForm: React.FC = () => {
|
function formatDate(date: Date | null) {
|
||||||
|
if (!date) return "";
|
||||||
|
// 格式化为 YYYY-MM-DD
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||||
|
const d = date.getDate().toString().padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContentForm() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { id } = useParams<{ id: string }>();
|
const [sourceType, setSourceType] = useState<"friends" | "groups">("friends");
|
||||||
const [form] = Form.useForm();
|
const [name, setName] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [selectedFriends, setSelectedFriends] = useState<string[]>([]);
|
||||||
const [saving, setSaving] = useState(false);
|
const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
|
||||||
const [library, setLibrary] = useState<ContentLibrary | null>(null);
|
const [useAI, setUseAI] = useState(false);
|
||||||
const [sourceType, setSourceType] = useState<number>(1);
|
const [aiPrompt, setAIPrompt] = useState("");
|
||||||
|
const [enabled, setEnabled] = useState(true);
|
||||||
|
const [dateRange, setDateRange] = useState<[Date | null, Date | null]>([
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
]);
|
||||||
|
const [showStartPicker, setShowStartPicker] = useState(false);
|
||||||
|
const [showEndPicker, setShowEndPicker] = useState(false);
|
||||||
|
const [keywordsInclude, setKeywordsInclude] = useState("");
|
||||||
|
const [keywordsExclude, setKeywordsExclude] = useState("");
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const isEdit = !!id;
|
const handleSubmit = async (e?: React.FormEvent) => {
|
||||||
|
if (e) e.preventDefault();
|
||||||
// 获取内容库详情
|
if (!name.trim()) {
|
||||||
useEffect(() => {
|
Toast.show({ content: "请输入内容库名称", position: "top" });
|
||||||
if (isEdit && id) {
|
return;
|
||||||
fetchLibraryDetail();
|
|
||||||
}
|
}
|
||||||
}, [isEdit, id]);
|
setSubmitting(true);
|
||||||
|
|
||||||
const fetchLibraryDetail = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const response = await getContentLibraryDetail(id!);
|
const payload = {
|
||||||
if (response.code === 200 && response.data) {
|
name,
|
||||||
setLibrary(response.data);
|
sourceType: sourceType === "friends" ? 1 : 2,
|
||||||
setSourceType(response.data.sourceType);
|
friends: selectedFriends,
|
||||||
|
groups: selectedGroups,
|
||||||
// 填充表单数据
|
groupMembers: {},
|
||||||
form.setFieldsValue({
|
keywordInclude: keywordsInclude
|
||||||
name: response.data.name,
|
.split(/,|,|\n|\s+/)
|
||||||
sourceType: response.data.sourceType,
|
.map((s) => s.trim())
|
||||||
keywordInclude: response.data.keywordInclude?.join(",") || "",
|
.filter(Boolean),
|
||||||
keywordExclude: response.data.keywordExclude?.join(",") || "",
|
keywordExclude: keywordsExclude
|
||||||
aiPrompt: response.data.aiPrompt || "",
|
.split(/,|,|\n|\s+/)
|
||||||
timeEnabled: response.data.timeEnabled === 1,
|
.map((s) => s.trim())
|
||||||
timeStart: response.data.timeStart || "09:00",
|
.filter(Boolean),
|
||||||
timeEnd: response.data.timeEnd || "18:00",
|
aiPrompt,
|
||||||
});
|
timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0,
|
||||||
} else {
|
startTime: dateRange[0] ? formatDate(dateRange[0]) : "",
|
||||||
Toast.show({
|
endTime: dateRange[1] ? formatDate(dateRange[1]) : "",
|
||||||
content: response.msg || "获取内容库详情失败",
|
status: enabled ? 1 : 0,
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("获取内容库详情失败:", error);
|
|
||||||
Toast.show({
|
|
||||||
content: error?.message || "请检查网络连接",
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
await request("/v1/content/library/create", payload, "POST");
|
||||||
const handleSubmit = async (values: any) => {
|
Toast.show({ content: "创建成功", position: "top" });
|
||||||
setSaving(true);
|
|
||||||
try {
|
|
||||||
const params: CreateContentLibraryParams = {
|
|
||||||
name: values.name,
|
|
||||||
sourceType: values.sourceType,
|
|
||||||
keywordInclude: values.keywordInclude
|
|
||||||
? values.keywordInclude
|
|
||||||
.split(",")
|
|
||||||
.map((k: string) => k.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [],
|
|
||||||
keywordExclude: values.keywordExclude
|
|
||||||
? values.keywordExclude
|
|
||||||
.split(",")
|
|
||||||
.map((k: string) => k.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
: [],
|
|
||||||
aiPrompt: values.aiPrompt || "",
|
|
||||||
timeEnabled: values.timeEnabled ? 1 : 0,
|
|
||||||
timeStart: values.timeStart || "09:00",
|
|
||||||
timeEnd: values.timeEnd || "18:00",
|
|
||||||
};
|
|
||||||
|
|
||||||
let response;
|
|
||||||
if (isEdit) {
|
|
||||||
response = await updateContentLibrary({
|
|
||||||
id: id!,
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response = await createContentLibrary(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.code === 200) {
|
|
||||||
Toast.show({
|
|
||||||
content: isEdit ? "更新成功" : "创建成功",
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
navigate("/content");
|
navigate("/content");
|
||||||
} else {
|
} catch (e: any) {
|
||||||
Toast.show({
|
Toast.show({ content: e?.message || "创建失败", position: "top" });
|
||||||
content: response.msg || (isEdit ? "更新失败" : "创建失败"),
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("保存内容库失败:", error);
|
|
||||||
Toast.show({
|
|
||||||
content: error?.message || "请检查网络连接",
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
|
||||||
navigate("/content");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
header={<NavCommon title={isEdit ? "编辑内容库" : "新建内容库"} />}
|
header={<NavCommon title="新建内容库" />}
|
||||||
>
|
footer={
|
||||||
<div className={style["loading"]}>
|
<div style={{ padding: "16px", backgroundColor: "#fff" }}>
|
||||||
<SpinLoading color="primary" style={{ fontSize: 32 }} />
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout header={<NavCommon title={isEdit ? "编辑内容库" : "新建内容库"} />}>
|
|
||||||
<div className={style["form-page"]}>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={handleSubmit}
|
|
||||||
className={style["form"]}
|
|
||||||
initialValues={{
|
|
||||||
sourceType: 1,
|
|
||||||
timeEnabled: false,
|
|
||||||
timeStart: "09:00",
|
|
||||||
timeEnd: "18:00",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 基本信息 */}
|
|
||||||
<Card className={style["form-card"]}>
|
|
||||||
<div className={style["card-title"]}>基本信息</div>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
label="内容库名称"
|
|
||||||
rules={[{ required: true, message: "请输入内容库名称" }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="请输入内容库名称" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="sourceType"
|
|
||||||
label="数据来源"
|
|
||||||
rules={[{ required: true, message: "请选择数据来源" }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="请选择数据来源"
|
|
||||||
onChange={(value) => setSourceType(value)}
|
|
||||||
>
|
|
||||||
<Option value={1}>
|
|
||||||
<Space>
|
|
||||||
<UserOutlined />
|
|
||||||
微信好友
|
|
||||||
</Space>
|
|
||||||
</Option>
|
|
||||||
<Option value={2}>
|
|
||||||
<Space>
|
|
||||||
<TeamOutlined />
|
|
||||||
聊天群
|
|
||||||
</Space>
|
|
||||||
</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 关键词设置 */}
|
|
||||||
<Card className={style["form-card"]}>
|
|
||||||
<div className={style["card-title"]}>关键词设置</div>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="keywordInclude"
|
|
||||||
label="包含关键词"
|
|
||||||
extra="多个关键词用逗号分隔"
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
placeholder="请输入包含的关键词,多个用逗号分隔"
|
|
||||||
rows={3}
|
|
||||||
className={style["textarea"]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="keywordExclude"
|
|
||||||
label="排除关键词"
|
|
||||||
extra="多个关键词用逗号分隔"
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
placeholder="请输入排除的关键词,多个用逗号分隔"
|
|
||||||
rows={3}
|
|
||||||
className={style["textarea"]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* AI 设置 */}
|
|
||||||
<Card className={style["form-card"]}>
|
|
||||||
<div className={style["card-title"]}>AI 设置</div>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="aiPrompt"
|
|
||||||
label="AI 提示词"
|
|
||||||
extra="用于AI处理内容的提示词"
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
placeholder="请输入AI提示词"
|
|
||||||
rows={4}
|
|
||||||
className={style["textarea"]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 时间设置 */}
|
|
||||||
<Card className={style["form-card"]}>
|
|
||||||
<div className={style["card-title"]}>时间设置</div>
|
|
||||||
|
|
||||||
<Form.Item name="timeEnabled" label="启用时间限制">
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
shouldUpdate={(prevValues, currentValues) =>
|
|
||||||
prevValues.timeEnabled !== currentValues.timeEnabled
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const timeEnabled = getFieldValue("timeEnabled");
|
|
||||||
return timeEnabled ? (
|
|
||||||
<div className={style["time-settings"]}>
|
|
||||||
<Form.Item
|
|
||||||
name="timeStart"
|
|
||||||
label="开始时间"
|
|
||||||
rules={[{ required: true, message: "请选择开始时间" }]}
|
|
||||||
>
|
|
||||||
<TimePicker
|
|
||||||
format="HH:mm"
|
|
||||||
placeholder="选择开始时间"
|
|
||||||
className={style["time-picker"]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="timeEnd"
|
|
||||||
label="结束时间"
|
|
||||||
rules={[{ required: true, message: "请选择结束时间" }]}
|
|
||||||
>
|
|
||||||
<TimePicker
|
|
||||||
format="HH:mm"
|
|
||||||
placeholder="选择结束时间"
|
|
||||||
className={style["time-picker"]}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
|
||||||
<div className={style["form-actions"]}>
|
|
||||||
<Button
|
|
||||||
fill="outline"
|
|
||||||
onClick={handleBack}
|
|
||||||
className={style["back-btn"]}
|
|
||||||
>
|
|
||||||
<ArrowLeftOutlined />
|
|
||||||
返回
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
|
block
|
||||||
color="primary"
|
color="primary"
|
||||||
type="submit"
|
loading={submitting}
|
||||||
loading={saving}
|
disabled={submitting}
|
||||||
className={style["submit-btn"]}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
<SaveOutlined />
|
创建内容库
|
||||||
{isEdit ? "更新" : "创建"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
}
|
||||||
|
>
|
||||||
|
<div className={style["form-page"]}>
|
||||||
|
<form
|
||||||
|
className={style["form-main"]}
|
||||||
|
onSubmit={(e) => e.preventDefault()}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div className={style["form-section"]}>
|
||||||
|
<label className={style["form-label"]}>
|
||||||
|
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||||
|
内容库名称
|
||||||
|
</label>
|
||||||
|
<AntdInput
|
||||||
|
placeholder="请输入内容库名称"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className={style["input"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["section-title"]}>数据来源配置</div>
|
||||||
|
<div className={style["form-section"]}>
|
||||||
|
<Tabs
|
||||||
|
activeKey={sourceType}
|
||||||
|
onChange={(key) => setSourceType(key as "friends" | "groups")}
|
||||||
|
className={style["tabs-bar"]}
|
||||||
|
>
|
||||||
|
<Tabs.Tab title="选择微信好友" key="friends">
|
||||||
|
<FriendSelection
|
||||||
|
selectedFriends={selectedFriends}
|
||||||
|
onSelect={setSelectedFriends}
|
||||||
|
placeholder="选择微信好友"
|
||||||
|
/>
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab title="选择聊天群" key="groups">
|
||||||
|
<GroupSelection
|
||||||
|
selectedGroups={selectedGroups}
|
||||||
|
onSelect={setSelectedGroups}
|
||||||
|
placeholder="选择聊天群"
|
||||||
|
/>
|
||||||
|
</Tabs.Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapse
|
||||||
|
defaultActiveKey={["keywords"]}
|
||||||
|
className={style["collapse"]}
|
||||||
|
>
|
||||||
|
<Collapse.Panel
|
||||||
|
key="keywords"
|
||||||
|
title={<span className={style["form-label"]}>关键词设置</span>}
|
||||||
|
>
|
||||||
|
<div className={style["form-section"]}>
|
||||||
|
<label className={style["form-label"]}>包含关键词</label>
|
||||||
|
<TextArea
|
||||||
|
placeholder="多个关键词用逗号分隔"
|
||||||
|
value={keywordsInclude}
|
||||||
|
onChange={(e) => setKeywordsInclude(e.target.value)}
|
||||||
|
className={style["input"]}
|
||||||
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style["form-section"]}>
|
||||||
|
<label className={style["form-label"]}>排除关键词</label>
|
||||||
|
<TextArea
|
||||||
|
placeholder="多个关键词用逗号分隔"
|
||||||
|
value={keywordsExclude}
|
||||||
|
onChange={(e) => setKeywordsExclude(e.target.value)}
|
||||||
|
className={style["input"]}
|
||||||
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<div className={style["section-title"]}>是否启用AI</div>
|
||||||
|
<div
|
||||||
|
className={style["form-section"]}
|
||||||
|
style={{ display: "flex", alignItems: "center", gap: 12 }}
|
||||||
|
>
|
||||||
|
<Switch checked={useAI} onChange={setUseAI} />
|
||||||
|
<span className={style["ai-desc"]}>
|
||||||
|
启用AI后,该内容库下的所有内容都会通过AI生成
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{useAI && (
|
||||||
|
<div className={style["form-section"]}>
|
||||||
|
<label className={style["form-label"]}>AI提示词</label>
|
||||||
|
<AntdInput
|
||||||
|
placeholder="请输入AI提示词"
|
||||||
|
value={aiPrompt}
|
||||||
|
onChange={(e) => setAIPrompt(e.target.value)}
|
||||||
|
className={style["input"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={style["section-title"]}>时间限制</div>
|
||||||
|
<div
|
||||||
|
className={style["form-section"]}
|
||||||
|
style={{ display: "flex", gap: 12 }}
|
||||||
|
>
|
||||||
|
<label>开始时间</label>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<AntdInput
|
||||||
|
readOnly
|
||||||
|
value={dateRange[0] ? dateRange[0].toLocaleDateString() : ""}
|
||||||
|
placeholder="年/月/日"
|
||||||
|
className={style["input"]}
|
||||||
|
onClick={() => setShowStartPicker(true)}
|
||||||
|
/>
|
||||||
|
<DatePicker
|
||||||
|
visible={showStartPicker}
|
||||||
|
title="开始时间"
|
||||||
|
value={dateRange[0]}
|
||||||
|
onClose={() => setShowStartPicker(false)}
|
||||||
|
onConfirm={(val) => {
|
||||||
|
setDateRange([val, dateRange[1]]);
|
||||||
|
setShowStartPicker(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label>结束时间</label>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<AntdInput
|
||||||
|
readOnly
|
||||||
|
value={dateRange[1] ? dateRange[1].toLocaleDateString() : ""}
|
||||||
|
placeholder="年/月/日"
|
||||||
|
className={style["input"]}
|
||||||
|
onClick={() => setShowEndPicker(true)}
|
||||||
|
/>
|
||||||
|
<DatePicker
|
||||||
|
visible={showEndPicker}
|
||||||
|
title="结束时间"
|
||||||
|
value={dateRange[1]}
|
||||||
|
onClose={() => setShowEndPicker(false)}
|
||||||
|
onConfirm={(val) => {
|
||||||
|
setDateRange([dateRange[0], val]);
|
||||||
|
setShowEndPicker(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={style["section-title"]}
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
marginBottom: 8,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>是否启用</span>
|
||||||
|
<Switch checked={enabled} onChange={setEnabled} />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ContentLibraryForm;
|
|
||||||
|
|||||||
@@ -69,42 +69,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-wrapper {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
flex:1;
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(22, 119, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: white;
|
|
||||||
color: #1677ff;
|
|
||||||
font-weight: 500;
|
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.library-list {
|
.library-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import NavCommon from "@/components/NavCommon";
|
|||||||
import { getContentLibraryList, deleteContentLibrary } from "./api";
|
import { getContentLibraryList, deleteContentLibrary } from "./api";
|
||||||
import { ContentLibrary } from "./data";
|
import { ContentLibrary } from "./data";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
import { Tabs } from "antd-mobile";
|
||||||
|
|
||||||
// 卡片菜单组件
|
// 卡片菜单组件
|
||||||
interface CardMenuProps {
|
interface CardMenuProps {
|
||||||
@@ -114,20 +115,9 @@ const ContentLibraryList: React.FC = () => {
|
|||||||
activeTab !== "all" ? (activeTab === "friends" ? 1 : 2) : undefined,
|
activeTab !== "all" ? (activeTab === "friends" ? 1 : 2) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.code === 200 && response.data) {
|
setLibraries(response.list || []);
|
||||||
setLibraries(response.data.list || []);
|
|
||||||
} else {
|
|
||||||
Toast.show({
|
|
||||||
content: response.msg || "获取内容库列表失败",
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("获取内容库列表失败:", error);
|
console.error("获取内容库列表失败:", error);
|
||||||
Toast.show({
|
|
||||||
content: error?.message || "请检查网络连接",
|
|
||||||
position: "top",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -196,69 +186,52 @@ const ContentLibraryList: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout header={<NavCommon title="内容库" />}>
|
<Layout
|
||||||
<div className={style["content-library-page"]}>
|
header={
|
||||||
{/* 搜索和操作栏 */}
|
<>
|
||||||
<div className={style["search-bar"]}>
|
<NavCommon
|
||||||
<div className={style["search-input-wrapper"]}>
|
title="内容库"
|
||||||
<SearchOutlined className={style["search-icon"]} />
|
right={
|
||||||
|
<Button size="small" color="primary" onClick={handleCreateNew}>
|
||||||
|
<PlusOutlined /> 新建内容库
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<div className="search-bar">
|
||||||
|
<div className="search-input-wrapper">
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索内容库..."
|
placeholder="搜索内容库"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
onPressEnter={handleSearch}
|
prefix={<SearchOutlined />}
|
||||||
className={style["search-input"]}
|
allowClear
|
||||||
|
size="large"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={loading}
|
loading={loading}
|
||||||
className={style["refresh-btn"]}
|
className="refresh-btn"
|
||||||
>
|
>
|
||||||
<ReloadOutlined className={loading ? style["spinning"] : ""} />
|
<ReloadOutlined />
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={handleCreateNew}
|
|
||||||
className={style["create-btn"]}
|
|
||||||
>
|
|
||||||
<PlusOutlined />
|
|
||||||
新建
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 标签页 */}
|
{/* 标签页 */}
|
||||||
<div className={style["tabs-wrapper"]}>
|
|
||||||
<div className={style["tabs"]}>
|
<div className={style["tabs"]}>
|
||||||
<div
|
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
||||||
className={`${style["tab"]} ${
|
<Tabs.Tab title="全部" key="all" />
|
||||||
activeTab === "all" ? style["active"] : ""
|
<Tabs.Tab title="微信好友" key="friends" />
|
||||||
}`}
|
<Tabs.Tab title="聊天群" key="groups" />
|
||||||
onClick={() => setActiveTab("all")}
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
全部
|
<div className={style["content-library-page"]}>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${style["tab"]} ${
|
|
||||||
activeTab === "friends" ? style["active"] : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("friends")}
|
|
||||||
>
|
|
||||||
微信好友
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${style["tab"]} ${
|
|
||||||
activeTab === "groups" ? style["active"] : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab("groups")}
|
|
||||||
>
|
|
||||||
聊天群
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 内容库列表 */}
|
{/* 内容库列表 */}
|
||||||
<div className={style["library-list"]}>
|
<div className={style["library-list"]}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user