移除冗余文件并重构内容管理模块:删除技术栈和兼容性说明文档,移除多个模态框组件,整合管理组件为统一结构,优化代码组织,提升可维护性。

This commit is contained in:
超级老白兔
2025-09-28 14:52:05 +08:00
parent a73481808c
commit 3b063ff64c
20 changed files with 639 additions and 978 deletions

View File

@@ -18,7 +18,7 @@ const IndexPage: React.FC = () => {
if (isMobile()) {
navigate("/mobile/dashboard");
} else {
navigate("/pc/dashboard");
navigate("/pc/weChat");
}
}, [navigate]);

View File

@@ -1,162 +0,0 @@
import React, { useState } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import { addKeyword, type KeywordAddRequest } from "./api";
const { TextArea } = Input;
const { Option } = Select;
interface AddKeywordModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
}
const AddKeywordModal: React.FC<AddKeywordModalProps> = ({
visible,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: KeywordAddRequest = {
title: values.title,
keywords: values.keywords,
content: values.content,
matchType: values.matchType,
priority: values.priority,
replyType: values.replyType,
status: values.status || "1",
};
const response = await addKeyword(data);
if (response) {
message.success("添加关键词成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加关键词失败");
}
} catch (error) {
console.error("添加关键词失败:", error);
message.error("添加关键词失败");
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
return (
<Modal
title="添加关键词回复"
open={visible}
onCancel={handleCancel}
footer={null}
width={600}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
status: "1",
matchType: "模糊匹配",
priority: "1",
replyType: "text",
}}
>
<Form.Item
name="title"
label="关键词标题"
rules={[{ required: true, message: "请输入关键词标题" }]}
>
<Input placeholder="请输入关键词标题" />
</Form.Item>
<Form.Item
name="keywords"
label="关键词"
rules={[{ required: true, message: "请输入关键词" }]}
>
<Input placeholder="请输入关键词" />
</Form.Item>
<Form.Item
name="content"
label="回复内容"
rules={[{ required: true, message: "请输入回复内容" }]}
>
<TextArea rows={4} placeholder="请输入回复内容" />
</Form.Item>
<Form.Item
name="matchType"
label="匹配类型"
rules={[{ required: true, message: "请选择匹配类型" }]}
>
<Select placeholder="请选择匹配类型">
<Option value="模糊匹配"></Option>
<Option value="精确匹配"></Option>
</Select>
</Form.Item>
<Form.Item
name="priority"
label="优先级"
rules={[{ required: true, message: "请选择优先级" }]}
>
<Select placeholder="请选择优先级">
<Option value="1">1</Option>
<Option value="2">2</Option>
<Option value="3">3</Option>
<Option value="4">4</Option>
<Option value="5">5</Option>
</Select>
</Form.Item>
<Form.Item
name="replyType"
label="回复类型"
rules={[{ required: true, message: "请选择回复类型" }]}
>
<Select placeholder="请选择回复类型">
<Option value="text"></Option>
<Option value="template"></Option>
</Select>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: "请选择状态" }]}
>
<Select placeholder="请选择状态">
<Option value="1"></Option>
<Option value="0"></Option>
</Select>
</Form.Item>
<Form.Item>
<div
style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}
>
<Button onClick={handleCancel}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default AddKeywordModal;

View File

@@ -1,174 +0,0 @@
import React, { useState } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import { addMaterial, type MaterialAddRequest } from "./api";
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
const { TextArea } = Input;
const { Option } = Select;
interface AddMaterialModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
}
const AddMaterialModal: React.FC<AddMaterialModalProps> = ({
visible,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [materialType, setMaterialType] = useState<string>("文本");
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: MaterialAddRequest = {
title: values.title,
content: [values.content],
cover: Array.isArray(values.cover)
? values.cover[0] || ""
: values.cover || "",
status: values.status || "1",
type: values.type || "文本",
};
const response = await addMaterial(data);
if (response) {
message.success("添加素材成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加素材失败");
}
} catch (error) {
console.error("添加素材失败:", error);
message.error("添加素材失败");
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
setMaterialType("文本");
onCancel();
};
return (
<Modal
title="添加素材"
open={visible}
onCancel={handleCancel}
footer={null}
width={600}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ status: "1", type: "文本" }}
onValuesChange={changedValues => {
if (changedValues.type) {
setMaterialType(changedValues.type);
}
}}
>
<Form.Item
name="title"
label="素材标题"
rules={[{ required: true, message: "请输入素材标题" }]}
>
<Input placeholder="请输入素材标题" />
</Form.Item>
<Form.Item
name="type"
label="素材类型"
rules={[{ required: true, message: "请选择素材类型" }]}
>
<Select placeholder="请选择素材类型">
<Option value="文本"></Option>
<Option value="图片"></Option>
<Option value="视频"></Option>
</Select>
</Form.Item>
{materialType === "文本" && (
<Form.Item
name="content"
label="素材内容"
rules={[{ required: true, message: "请输入素材内容" }]}
>
<TextArea
rows={4}
placeholder="请输入素材内容,多个内容用换行分隔"
/>
</Form.Item>
)}
{materialType === "图片" && (
<Form.Item
name="content"
label="图片文件"
rules={[{ required: true, message: "请上传图片文件" }]}
>
<ImageUpload
count={1}
accept="image/*"
className="material-cover-upload"
/>
</Form.Item>
)}
{materialType === "视频" && (
<Form.Item
name="content"
label="视频文件"
rules={[{ required: true, message: "请上传视频文件" }]}
>
<ImageUpload
count={1}
accept="video/*"
className="material-cover-upload"
/>
</Form.Item>
)}
<Form.Item name="cover" label="封面图片">
<ImageUpload
count={1}
accept="image/*"
className="material-cover-upload"
/>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: "请选择状态" }]}
>
<Select placeholder="请选择状态">
<Option value="1"></Option>
<Option value="0"></Option>
</Select>
</Form.Item>
<Form.Item>
<div
style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}
>
<Button onClick={handleCancel}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default AddMaterialModal;

View File

@@ -1,133 +0,0 @@
import React, { useState } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import { addSensitiveWord, type SensitiveWordAddRequest } from "./api";
const { TextArea } = Input;
const { Option } = Select;
interface AddSensitiveWordModalProps {
visible: boolean;
onCancel: () => void;
onSuccess: () => void;
}
const AddSensitiveWordModal: React.FC<AddSensitiveWordModalProps> = ({
visible,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: SensitiveWordAddRequest = {
title: values.title,
keywords: values.keywords,
content: values.content,
operation: values.operation,
status: values.status || "1",
};
const response = await addSensitiveWord(data);
if (response) {
message.success("添加敏感词成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加敏感词失败");
}
} catch (error) {
console.error("添加敏感词失败:", error);
message.error("添加敏感词失败");
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
return (
<Modal
title="添加敏感词"
open={visible}
onCancel={handleCancel}
footer={null}
width={600}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ status: "1", operation: "1" }}
>
<Form.Item
name="title"
label="敏感词标题"
rules={[{ required: true, message: "请输入敏感词标题" }]}
>
<Input placeholder="请输入敏感词标题" />
</Form.Item>
<Form.Item
name="keywords"
label="关键词"
rules={[{ required: true, message: "请输入关键词" }]}
>
<Input placeholder="请输入关键词" />
</Form.Item>
<Form.Item
name="content"
label="敏感词内容"
rules={[{ required: true, message: "请输入敏感词内容" }]}
>
<TextArea rows={4} placeholder="请输入敏感词内容" />
</Form.Item>
<Form.Item
name="operation"
label="操作类型"
rules={[{ required: true, message: "请选择操作类型" }]}
>
<Select placeholder="请选择操作类型">
<Option value="0"></Option>
<Option value="1"></Option>
<Option value="2"></Option>
<Option value="3"></Option>
<Option value="4"></Option>
</Select>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: "请选择状态" }]}
>
<Select placeholder="请选择状态">
<Option value="1"></Option>
<Option value="0"></Option>
</Select>
</Form.Item>
<Form.Item>
<div
style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}
>
<Button onClick={handleCancel}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default AddSensitiveWordModal;

View File

@@ -1,211 +0,0 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import {
updateMaterial,
getMaterialDetails,
type MaterialUpdateRequest,
} from "./api";
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
const { TextArea } = Input;
const { Option } = Select;
interface EditMaterialModalProps {
visible: boolean;
materialId: number | null;
onCancel: () => void;
onSuccess: () => void;
}
const EditMaterialModal: React.FC<EditMaterialModalProps> = ({
visible,
materialId,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [materialType, setMaterialType] = useState<string>("文本");
// 获取素材详情
const fetchMaterialDetails = async (id: number) => {
try {
const response = await getMaterialDetails(id.toString());
if (response) {
const material = response;
form.setFieldsValue({
title: material.title,
content: Array.isArray(material.content)
? material.content.join("\n")
: material.content,
cover: material.cover ? [material.cover] : [],
status: material.status.toString(),
type: material.type || "文本",
});
setMaterialType(material.type || "文本");
}
} catch (error) {
console.error("获取素材详情失败:", error);
message.error("获取素材详情失败");
}
};
// 当弹窗打开且有ID时获取详情
useEffect(() => {
if (visible && materialId) {
fetchMaterialDetails(materialId);
}
}, [visible, materialId]);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: MaterialUpdateRequest = {
id: materialId?.toString(),
title: values.title,
content: [values.content],
cover: Array.isArray(values.cover)
? values.cover[0] || ""
: values.cover || "",
status: values.status || "1",
type: values.type || "文本",
};
const response = await updateMaterial(data);
if (response) {
message.success("更新素材成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "更新素材失败");
}
} catch (error) {
console.error("更新素材失败:", error);
message.error("更新素材失败");
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
setMaterialType("文本");
onCancel();
};
return (
<Modal
title="编辑素材"
open={visible}
onCancel={handleCancel}
footer={null}
width={600}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ status: "1", type: "文本" }}
onValuesChange={changedValues => {
if (changedValues.type) {
setMaterialType(changedValues.type);
}
}}
>
<Form.Item
name="title"
label="素材标题"
rules={[{ required: true, message: "请输入素材标题" }]}
>
<Input placeholder="请输入素材标题" />
</Form.Item>
<Form.Item
name="type"
label="素材类型"
rules={[{ required: true, message: "请选择素材类型" }]}
>
<Select placeholder="请选择素材类型">
<Option value="文本"></Option>
<Option value="图片"></Option>
<Option value="视频"></Option>
</Select>
</Form.Item>
{materialType === "文本" && (
<Form.Item
name="content"
label="素材内容"
rules={[{ required: true, message: "请输入素材内容" }]}
>
<TextArea
rows={4}
placeholder="请输入素材内容,多个内容用换行分隔"
/>
</Form.Item>
)}
{materialType === "图片" && (
<Form.Item
name="content"
label="图片文件"
rules={[{ required: true, message: "请上传图片文件" }]}
>
<ImageUpload
count={1}
accept="image/*"
className="material-cover-upload"
/>
</Form.Item>
)}
{materialType === "视频" && (
<Form.Item
name="content"
label="视频文件"
rules={[{ required: true, message: "请上传视频文件" }]}
>
<ImageUpload
count={1}
accept="video/*"
className="material-cover-upload"
/>
</Form.Item>
)}
<Form.Item name="cover" label="封面图片">
<ImageUpload
count={1}
accept="image/*"
className="material-cover-upload"
/>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: "请选择状态" }]}
>
<Select placeholder="请选择状态">
<Option value="1"></Option>
<Option value="0"></Option>
</Select>
</Form.Item>
<Form.Item>
<div
style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}
>
<Button onClick={handleCancel}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default EditMaterialModal;

View File

@@ -7,13 +7,22 @@ export interface MaterialListParams {
page?: string;
}
// 内容项类型定义
export interface ContentItem {
type: "text" | "image" | "video" | "file" | "audio" | "link";
data: string | LinkData;
}
// 链接数据类型
export interface LinkData {
url: string;
}
export interface MaterialAddRequest {
content: string[];
cover: string;
status: string;
title: string;
type: string; // 素材类型:文本、图片、视频
[property: string]: any;
cover?: string;
status: number;
content: ContentItem[];
}
export interface MaterialUpdateRequest extends MaterialAddRequest {

View File

@@ -1,3 +1,9 @@
export { default as MaterialManagement } from "../MaterialManagement";
export { default as SensitiveWordManagement } from "../SensitiveWordManagement";
export { default as KeywordManagement } from "../KeywordManagement";
// 管理组件导出
export { default as MaterialManagement } from "./management/MaterialManagement";
export { default as SensitiveWordManagement } from "./management/SensitiveWordManagement";
export { default as KeywordManagement } from "./management/KeywordManagement";
// 模态框组件导出
export { default as MaterialModal } from "./modals/MaterialModal";
export { default as SensitiveWordModal } from "./modals/SensitiveWordModal";
export { default as KeywordModal } from "./modals/KeywordModal";

View File

@@ -6,14 +6,14 @@ import {
FormOutlined,
DeleteOutlined,
} from "@ant-design/icons";
import styles from "./index.module.scss";
import styles from "../../index.module.scss";
import {
getKeywordList,
deleteKeyword,
setKeywordStatus,
type KeywordListParams,
} from "./api";
import EditKeywordModal from "./EditKeywordModal";
} from "../../api";
import KeywordModal from "../modals/KeywordModal";
const { Search } = Input;
@@ -210,8 +210,9 @@ const KeywordManagement: React.FC = () => {
</div>
{/* 编辑弹窗 */}
<EditKeywordModal
<KeywordModal
visible={editModalVisible}
mode="edit"
keywordId={editingKeywordId}
onCancel={() => {
setEditModalVisible(false);

View File

@@ -1,4 +1,9 @@
import React, { useState, useEffect } from "react";
import React, {
useState,
useEffect,
forwardRef,
useImperativeHandle,
} from "react";
import { Button, Input, Card, message, Switch, Tag } from "antd";
import {
SearchOutlined,
@@ -8,14 +13,14 @@ import {
FileImageOutlined,
PlayCircleOutlined,
} from "@ant-design/icons";
import styles from "./index.module.scss";
import styles from "../../index.module.scss";
import {
getMaterialList,
deleteMaterial,
setMaterialStatus,
type MaterialListParams,
} from "./api";
import EditMaterialModal from "./EditMaterialModal";
} from "../../api";
import MaterialModal from "../modals/MaterialModal";
const { Search } = Input;
@@ -35,7 +40,7 @@ interface MaterialItem {
userName: string;
}
const MaterialManagement: React.FC = () => {
const MaterialManagement = forwardRef<any, {}>((props, ref) => {
const [searchValue, setSearchValue] = useState<string>("");
const [materialsList, setMaterialsList] = useState<MaterialItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
@@ -92,6 +97,11 @@ const MaterialManagement: React.FC = () => {
}
};
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
fetchMaterials,
}));
// 素材管理相关函数
const handleDeleteMaterial = async (id: number) => {
try {
@@ -244,8 +254,9 @@ const MaterialManagement: React.FC = () => {
</div>
{/* 编辑弹窗 */}
<EditMaterialModal
<MaterialModal
visible={editModalVisible}
mode="edit"
materialId={editingMaterialId}
onCancel={() => {
setEditModalVisible(false);
@@ -255,6 +266,6 @@ const MaterialManagement: React.FC = () => {
/>
</div>
);
};
});
export default MaterialManagement;

View File

@@ -6,14 +6,14 @@ import {
FormOutlined,
DeleteOutlined,
} from "@ant-design/icons";
import styles from "./index.module.scss";
import styles from "../../index.module.scss";
import {
getSensitiveWordList,
deleteSensitiveWord,
setSensitiveWordStatus,
type SensitiveWordListParams,
} from "./api";
import EditSensitiveWordModal from "./EditSensitiveWordModal";
} from "../../api";
import SensitiveWordModal from "../modals/SensitiveWordModal";
const { Search } = Input;
@@ -215,8 +215,9 @@ const SensitiveWordManagement: React.FC = () => {
</div>
{/* 编辑弹窗 */}
<EditSensitiveWordModal
<SensitiveWordModal
visible={editModalVisible}
mode="edit"
sensitiveWordId={editingSensitiveWordId}
onCancel={() => {
setEditModalVisible(false);

View File

@@ -0,0 +1,4 @@
// 管理组件统一导出
export { default as MaterialManagement } from "./MaterialManagement";
export { default as SensitiveWordManagement } from "./SensitiveWordManagement";
export { default as KeywordManagement } from "./KeywordManagement";

View File

@@ -0,0 +1,244 @@
import React, { useState, useEffect } from "react";
import { Button, Input, Select } from "antd";
import {
PlusOutlined,
DeleteOutlined,
FileTextOutlined,
FileImageOutlined,
PlayCircleOutlined,
FileOutlined,
SoundOutlined,
LinkOutlined,
} from "@ant-design/icons";
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
import VideoUpload from "@/components/Upload/VideoUpload";
import FileUpload from "@/components/Upload/FileUpload";
import type { ContentItem } from "../../api";
const { TextArea } = Input;
const { Option } = Select;
interface ContentManagerProps {
value?: ContentItem[];
onChange?: (content: ContentItem[]) => void;
}
const ContentManager: React.FC<ContentManagerProps> = ({
value = [],
onChange,
}) => {
const [contentItems, setContentItems] = useState<ContentItem[]>(value);
// 内容类型配置
const contentTypes = [
{ value: "text", label: "文本", icon: <FileTextOutlined /> },
{ value: "image", label: "图片", icon: <FileImageOutlined /> },
{ value: "video", label: "视频", icon: <PlayCircleOutlined /> },
{ value: "file", label: "文件", icon: <FileOutlined /> },
{ value: "audio", label: "音频", icon: <SoundOutlined /> },
{ value: "link", label: "链接", icon: <LinkOutlined /> },
];
// 同步外部value到内部state
useEffect(() => {
setContentItems(value);
}, [value]);
// 初始化时添加默认文本内容项
useEffect(() => {
if (contentItems.length === 0) {
const defaultTextItem: ContentItem = {
type: "text",
data: "",
};
setContentItems([defaultTextItem]);
onChange?.([defaultTextItem]);
}
}, [contentItems.length, onChange]);
// 更新内容项
const updateContentItems = (newItems: ContentItem[]) => {
setContentItems(newItems);
onChange?.(newItems);
};
// 添加新内容项
const handleAddItem = () => {
const newItem: ContentItem = {
type: "text",
data: "",
};
const newItems = [...contentItems, newItem];
updateContentItems(newItems);
};
// 删除内容项
const handleDeleteItem = (index: number) => {
const newItems = contentItems.filter((_, i) => i !== index);
updateContentItems(newItems);
};
// 更新内容项数据
const updateItemData = (index: number, data: any) => {
const newItems = [...contentItems];
newItems[index] = { ...newItems[index], data };
updateContentItems(newItems);
};
// 更新内容项类型
const updateItemType = (index: number, newType: string) => {
const newItems = [...contentItems];
// 根据新类型重置数据
let newData: any;
if (newType === "link") {
newData = "";
} else {
newData = "";
}
newItems[index] = {
type: newType as any,
data: newData,
};
updateContentItems(newItems);
};
// 渲染内容项
const renderContentItem = (item: ContentItem, index: number) => {
return (
<div
key={index}
style={{
border: "1px solid #d9d9d9",
borderRadius: "6px",
padding: "12px",
marginBottom: "8px",
backgroundColor: "#fafafa",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "8px",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Select
value={item.type}
onChange={newType => updateItemType(index, newType)}
style={{ width: 120 }}
size="small"
>
{contentTypes.map(type => (
<Option key={type.value} value={type.value}>
{type.icon} {type.label}
</Option>
))}
</Select>
</div>
{index !== 0 && (
<Button
type="link"
danger
size="small"
icon={<DeleteOutlined />}
onClick={() => handleDeleteItem(index)}
/>
)}
</div>
{renderContentInput(item, index)}
</div>
);
};
// 渲染内容输入
const renderContentInput = (item: ContentItem, index: number) => {
switch (item.type) {
case "text":
return (
<TextArea
value={item.data as string}
onChange={e => updateItemData(index, e.target.value)}
placeholder="请输入文本内容"
rows={3}
/>
);
case "image":
return (
<ImageUpload
count={1}
accept="image/*"
value={item.data ? [item.data as string] : []}
onChange={urls => updateItemData(index, urls[0] || "")}
/>
);
case "video":
return (
<VideoUpload
value={item.data as string}
onChange={url => updateItemData(index, url)}
maxSize={50}
showPreview={true}
/>
);
case "file":
return (
<FileUpload
value={item.data as string}
onChange={url => updateItemData(index, url)}
maxSize={10}
showPreview={true}
acceptTypes={["excel", "word", "ppt", "pdf", "txt"]}
/>
);
case "audio":
return (
<FileUpload
value={item.data as string}
onChange={url => updateItemData(index, url)}
maxSize={50}
showPreview={true}
acceptTypes={["mp3", "wav", "aac", "m4a", "ogg"]}
/>
);
case "link": {
return (
<div>
<Input
value={item.data as string}
onChange={e => updateItemData(index, e.target.value)}
placeholder="链接URL"
style={{ marginBottom: 8 }}
/>
</div>
);
}
default:
return null;
}
};
return (
<div>
{/* 内容列表 */}
{contentItems.map((item, index) => renderContentItem(item, index))}
{/* 添加内容区域 */}
<Button
type="dashed"
block
icon={<PlusOutlined />}
onClick={handleAddItem}
>
</Button>
</div>
);
};
export default ContentManager;

View File

@@ -1,23 +1,27 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import {
addKeyword,
updateKeyword,
getKeywordDetails,
type KeywordAddRequest,
type KeywordUpdateRequest,
} from "./api";
} from "../../api";
const { TextArea } = Input;
const { Option } = Select;
interface EditKeywordModalProps {
interface KeywordModalProps {
visible: boolean;
keywordId: string | null;
mode: "add" | "edit";
keywordId?: string | null;
onCancel: () => void;
onSuccess: () => void;
}
const EditKeywordModal: React.FC<EditKeywordModalProps> = ({
const KeywordModal: React.FC<KeywordModalProps> = ({
visible,
mode,
keywordId,
onCancel,
onSuccess,
@@ -47,39 +51,65 @@ const EditKeywordModal: React.FC<EditKeywordModalProps> = ({
}
};
// 当弹窗打开且有ID时,获取详情
// 当弹窗打开且为编辑模式时,获取详情
useEffect(() => {
if (visible && keywordId) {
if (visible && mode === "edit" && keywordId) {
fetchKeywordDetails(keywordId);
} else if (visible && mode === "add") {
// 添加模式时重置表单
form.resetFields();
}
}, [visible, keywordId]);
}, [visible, mode, keywordId]);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: KeywordUpdateRequest = {
id: keywordId,
title: values.title,
keywords: values.keywords,
content: values.content,
matchType: values.matchType,
priority: values.priority,
replyType: values.replyType,
status: values.status,
};
const response = await updateKeyword(data);
if (response) {
message.success("更新关键词回复成功");
form.resetFields();
onSuccess();
onCancel();
if (mode === "add") {
const data: KeywordAddRequest = {
title: values.title,
keywords: values.keywords,
content: values.content,
matchType: values.matchType,
priority: values.priority,
replyType: values.replyType,
status: values.status || "1",
};
const response = await addKeyword(data);
if (response) {
message.success("添加关键词成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加关键词失败");
}
} else {
message.error(response?.message || "更新关键词回复失败");
const data: KeywordUpdateRequest = {
id: keywordId,
title: values.title,
keywords: values.keywords,
content: values.content,
matchType: values.matchType,
priority: values.priority,
replyType: values.replyType,
status: values.status,
};
const response = await updateKeyword(data);
if (response) {
message.success("更新关键词回复成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "更新关键词回复失败");
}
}
} catch (error) {
console.error("更新关键词回复失败:", error);
message.error("更新关键词回复失败");
console.error(`${mode === "add" ? "添加" : "更新"}关键词失败:`, error);
message.error(`${mode === "add" ? "添加" : "更新"}关键词失败`);
} finally {
setLoading(false);
}
@@ -90,9 +120,11 @@ const EditKeywordModal: React.FC<EditKeywordModalProps> = ({
onCancel();
};
const title = mode === "add" ? "添加关键词回复" : "编辑关键词回复";
return (
<Modal
title="编辑关键词回复"
title={title}
open={visible}
onCancel={handleCancel}
footer={null}
@@ -195,4 +227,4 @@ const EditKeywordModal: React.FC<EditKeywordModalProps> = ({
);
};
export default EditKeywordModal;
export default KeywordModal;

View File

@@ -0,0 +1,194 @@
import React, { useState, useEffect, useCallback } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import {
addMaterial,
updateMaterial,
getMaterialDetails,
type MaterialAddRequest,
type MaterialUpdateRequest,
type ContentItem,
} from "../../api";
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
import ContentManager from "./ContentManager";
const { Option } = Select;
interface MaterialModalProps {
visible: boolean;
mode: "add" | "edit";
materialId?: number | null;
onCancel: () => void;
onSuccess: () => void;
}
const MaterialModal: React.FC<MaterialModalProps> = ({
visible,
mode,
materialId,
onCancel,
onSuccess,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [contentItems, setContentItems] = useState<ContentItem[]>([]);
// 获取素材详情
const fetchMaterialDetails = useCallback(
async (id: number) => {
try {
const response = await getMaterialDetails(id.toString());
if (response) {
const material = response;
form.setFieldsValue({
title: material.title,
cover: material.cover ? [material.cover] : [],
status: material.status,
});
// 设置内容项
setContentItems(material.content || []);
}
} catch (error) {
console.error("获取素材详情失败:", error);
message.error("获取素材详情失败");
}
},
[form],
);
// 当弹窗打开且为编辑模式时,获取详情
useEffect(() => {
if (visible && mode === "edit" && materialId) {
fetchMaterialDetails(materialId);
} else if (visible && mode === "add") {
// 添加模式时重置表单
form.resetFields();
setContentItems([]);
}
}, [visible, mode, materialId, fetchMaterialDetails, form]);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
// 验证内容项
if (contentItems.length === 0) {
message.warning("请至少添加一个内容项");
return;
}
const coverValue = Array.isArray(values.cover)
? values.cover[0] || ""
: values.cover || "";
const data: MaterialAddRequest = {
title: values.title,
status: values.status || 1,
content: contentItems,
...(coverValue && { cover: coverValue }),
};
if (mode === "add") {
const response = await addMaterial(data);
if (response) {
message.success("添加素材成功");
form.resetFields();
setContentItems([]);
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加素材失败");
}
} else {
const updateData: MaterialUpdateRequest = {
...data,
id: materialId?.toString(),
};
const response = await updateMaterial(updateData);
if (response) {
message.success("更新素材成功");
form.resetFields();
setContentItems([]);
onSuccess();
onCancel();
} else {
message.error(response?.message || "更新素材失败");
}
}
} catch (error) {
console.error(`${mode === "add" ? "添加" : "更新"}素材失败:`, error);
message.error(`${mode === "add" ? "添加" : "更新"}素材失败`);
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
setContentItems([]);
onCancel();
};
const title = mode === "add" ? "添加素材" : "编辑素材";
return (
<Modal
title={title}
open={visible}
onCancel={handleCancel}
footer={null}
width={800}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ status: 1 }}
>
<Form.Item
name="title"
label="素材标题"
rules={[{ required: true, message: "请输入素材标题" }]}
>
<Input placeholder="请输入素材标题" />
</Form.Item>
<Form.Item label="素材内容">
<ContentManager value={contentItems} onChange={setContentItems} />
</Form.Item>
<Form.Item name="cover" label="封面图片">
<ImageUpload
count={1}
accept="image/*"
className="material-cover-upload"
/>
</Form.Item>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: "请选择状态" }]}
>
<Select placeholder="请选择状态">
<Option value={1}></Option>
<Option value={0}></Option>
</Select>
</Form.Item>
<Form.Item>
<div
style={{ display: "flex", justifyContent: "flex-end", gap: "8px" }}
>
<Button onClick={handleCancel}></Button>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</div>
</Form.Item>
</Form>
</Modal>
);
};
export default MaterialModal;

View File

@@ -1,23 +1,27 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Input, Button, message, Select } from "antd";
import {
addSensitiveWord,
updateSensitiveWord,
getSensitiveWordDetails,
type SensitiveWordAddRequest,
type SensitiveWordUpdateRequest,
} from "./api";
} from "../../api";
const { TextArea } = Input;
const { Option } = Select;
interface EditSensitiveWordModalProps {
interface SensitiveWordModalProps {
visible: boolean;
sensitiveWordId: string | null;
mode: "add" | "edit";
sensitiveWordId?: string | null;
onCancel: () => void;
onSuccess: () => void;
}
const EditSensitiveWordModal: React.FC<EditSensitiveWordModalProps> = ({
const SensitiveWordModal: React.FC<SensitiveWordModalProps> = ({
visible,
mode,
sensitiveWordId,
onCancel,
onSuccess,
@@ -45,37 +49,61 @@ const EditSensitiveWordModal: React.FC<EditSensitiveWordModalProps> = ({
}
};
// 当弹窗打开且有ID时,获取详情
// 当弹窗打开且为编辑模式时,获取详情
useEffect(() => {
if (visible && sensitiveWordId) {
if (visible && mode === "edit" && sensitiveWordId) {
fetchSensitiveWordDetails(sensitiveWordId);
} else if (visible && mode === "add") {
// 添加模式时重置表单
form.resetFields();
}
}, [visible, sensitiveWordId]);
}, [visible, mode, sensitiveWordId]);
const handleSubmit = async (values: any) => {
try {
setLoading(true);
const data: SensitiveWordUpdateRequest = {
id: sensitiveWordId,
title: values.title,
keywords: values.keywords,
content: values.content,
operation: values.operation,
status: values.status,
};
const response = await updateSensitiveWord(data);
if (response) {
message.success("更新敏感词成功");
form.resetFields();
onSuccess();
onCancel();
if (mode === "add") {
const data: SensitiveWordAddRequest = {
title: values.title,
keywords: values.keywords,
content: values.content,
operation: values.operation,
status: values.status || "1",
};
const response = await addSensitiveWord(data);
if (response) {
message.success("添加敏感词成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "添加敏感词失败");
}
} else {
message.error(response?.message || "更新敏感词失败");
const data: SensitiveWordUpdateRequest = {
id: sensitiveWordId,
title: values.title,
keywords: values.keywords,
content: values.content,
operation: values.operation,
status: values.status,
};
const response = await updateSensitiveWord(data);
if (response) {
message.success("更新敏感词成功");
form.resetFields();
onSuccess();
onCancel();
} else {
message.error(response?.message || "更新敏感词失败");
}
}
} catch (error) {
console.error("更新敏感词失败:", error);
message.error("更新敏感词失败");
console.error(`${mode === "add" ? "添加" : "更新"}敏感词失败:`, error);
message.error(`${mode === "add" ? "添加" : "更新"}敏感词失败`);
} finally {
setLoading(false);
}
@@ -86,9 +114,11 @@ const EditSensitiveWordModal: React.FC<EditSensitiveWordModalProps> = ({
onCancel();
};
const title = mode === "add" ? "添加敏感词" : "编辑敏感词";
return (
<Modal
title="编辑敏感词"
title={title}
open={visible}
onCancel={handleCancel}
footer={null}
@@ -164,4 +194,4 @@ const EditSensitiveWordModal: React.FC<EditSensitiveWordModalProps> = ({
);
};
export default EditSensitiveWordModal;
export default SensitiveWordModal;

View File

@@ -0,0 +1,5 @@
// 模态框组件统一导出
export { default as MaterialModal } from "./MaterialModal";
export { default as SensitiveWordModal } from "./SensitiveWordModal";
export { default as KeywordModal } from "./KeywordModal";
export { default as ContentManager } from "./ContentManager";

View File

@@ -1,14 +1,16 @@
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import { Button } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import PowerNavigation from "@/components/PowerNavtion";
import styles from "./index.module.scss";
import MaterialManagement from "./MaterialManagement";
import SensitiveWordManagement from "./SensitiveWordManagement";
import KeywordManagement from "./KeywordManagement";
import AddMaterialModal from "./AddMaterialModal";
import AddSensitiveWordModal from "./AddSensitiveWordModal";
import AddKeywordModal from "./AddKeywordModal";
import {
MaterialManagement,
SensitiveWordManagement,
KeywordManagement,
MaterialModal,
SensitiveWordModal,
KeywordModal,
} from "./components";
const ContentManagement: React.FC = () => {
const [activeTab, setActiveTab] = useState<string>("material");
@@ -17,6 +19,9 @@ const ContentManagement: React.FC = () => {
useState(false);
const [keywordModalVisible, setKeywordModalVisible] = useState(false);
// 引用素材管理组件
const materialManagementRef = useRef<any>(null);
const tabs = [
{ key: "material", label: "素材资源库" },
{ key: "sensitive", label: "敏感词管理" },
@@ -38,20 +43,22 @@ const ContentManagement: React.FC = () => {
// 弹窗成功回调
const handleModalSuccess = () => {
// 这里可以触发子组件重新获取数据
// 由于子组件是独立的,它们会在组件重新挂载时自动获取数据
// 刷新素材列表
if (materialManagementRef.current?.fetchMaterials) {
materialManagementRef.current.fetchMaterials();
}
};
const renderTabContent = () => {
switch (activeTab) {
case "material":
return <MaterialManagement />;
return <MaterialManagement ref={materialManagementRef} />;
case "sensitive":
return <SensitiveWordManagement />;
case "keyword":
return <KeywordManagement />;
default:
return <MaterialManagement />;
return <MaterialManagement ref={materialManagementRef} />;
}
};
@@ -101,20 +108,23 @@ const ContentManagement: React.FC = () => {
<div className={styles.content}>{renderTabContent()}</div>
{/* 弹窗组件 */}
<AddMaterialModal
<MaterialModal
visible={materialModalVisible}
mode="add"
onCancel={() => setMaterialModalVisible(false)}
onSuccess={handleModalSuccess}
/>
<AddSensitiveWordModal
<SensitiveWordModal
visible={sensitiveWordModalVisible}
mode="add"
onCancel={() => setSensitiveWordModalVisible(false)}
onSuccess={handleModalSuccess}
/>
<AddKeywordModal
<KeywordModal
visible={keywordModalVisible}
mode="add"
onCancel={() => setKeywordModalVisible(false)}
onSuccess={handleModalSuccess}
/>

View File

@@ -1,3 +0,0 @@
export { default as AddMaterialModal } from "../AddMaterialModal";
export { default as AddSensitiveWordModal } from "../AddSensitiveWordModal";
export { default as AddKeywordModal } from "../AddKeywordModal";