重构关键词管理模块:将关键词管理组件转换为使用forwardRef,增强与父组件的交互能力;优化关键词列表的获取和处理逻辑,提升用户体验。
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
} from "react";
|
||||||
import { Button, Input, Tag, Switch, message } from "antd";
|
import { Button, Input, Tag, Switch, message } from "antd";
|
||||||
import {
|
import {
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
@@ -29,199 +34,210 @@ interface KeywordItem {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeywordManagement: React.FC = () => {
|
const KeywordManagement = forwardRef<any, Record<string, never>>(
|
||||||
const [searchValue, setSearchValue] = useState<string>("");
|
(props, ref) => {
|
||||||
const [keywordsList, setKeywordsList] = useState<KeywordItem[]>([]);
|
const [searchValue, setSearchValue] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [keywordsList, setKeywordsList] = useState<KeywordItem[]>([]);
|
||||||
const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [editingKeywordId, setEditingKeywordId] = useState<string | null>(null);
|
const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
|
||||||
|
const [editingKeywordId, setEditingKeywordId] = useState<string | null>(
|
||||||
// 回复类型映射
|
null,
|
||||||
const getReplyTypeText = (replyType: string) => {
|
|
||||||
switch (replyType) {
|
|
||||||
case "text":
|
|
||||||
return "文本回复";
|
|
||||||
case "template":
|
|
||||||
return "模板回复";
|
|
||||||
default:
|
|
||||||
return "未知类型";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 回复类型颜色
|
|
||||||
const getReplyTypeColor = (replyType: string) => {
|
|
||||||
switch (replyType) {
|
|
||||||
case "text":
|
|
||||||
return "#1890ff";
|
|
||||||
case "template":
|
|
||||||
return "#722ed1";
|
|
||||||
default:
|
|
||||||
return "#8c8c8c";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取关键词列表
|
|
||||||
const fetchKeywords = async (params?: KeywordListParams) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const response = await getKeywordList(params || {});
|
|
||||||
if (response) {
|
|
||||||
setKeywordsList(response.list || []);
|
|
||||||
} else {
|
|
||||||
setKeywordsList([]);
|
|
||||||
message.error(response?.message || "获取关键词列表失败");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取关键词列表失败:", error);
|
|
||||||
setKeywordsList([]);
|
|
||||||
message.error("获取关键词列表失败");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关键词管理相关函数
|
|
||||||
const handleToggleKeyword = async (id: string) => {
|
|
||||||
try {
|
|
||||||
const response = await setKeywordStatus({ id });
|
|
||||||
if (response) {
|
|
||||||
setKeywordsList(prev =>
|
|
||||||
prev.map(item =>
|
|
||||||
item.id === id ? { ...item, enabled: !item.enabled } : item,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
message.success("状态更新成功");
|
|
||||||
} else {
|
|
||||||
message.error(response?.message || "状态更新失败");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("状态更新失败:", error);
|
|
||||||
message.error("状态更新失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditKeyword = (id: string) => {
|
|
||||||
setEditingKeywordId(id);
|
|
||||||
setEditModalVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编辑弹窗成功回调
|
|
||||||
const handleEditSuccess = () => {
|
|
||||||
fetchKeywords(); // 重新获取数据
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteKeyword = async (id: string) => {
|
|
||||||
try {
|
|
||||||
const response = await deleteKeyword(id);
|
|
||||||
if (response) {
|
|
||||||
setKeywordsList(prev => prev.filter(item => item.id !== id));
|
|
||||||
message.success("删除成功");
|
|
||||||
} else {
|
|
||||||
message.error(response?.message || "删除失败");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("删除失败:", error);
|
|
||||||
message.error("删除失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 搜索和筛选功能
|
|
||||||
const filteredKeywords = keywordsList.filter(item => {
|
|
||||||
if (!searchValue) return true;
|
|
||||||
return (
|
|
||||||
item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
||||||
item.keywords.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
||||||
item.content.toLowerCase().includes(searchValue.toLowerCase())
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// 搜索处理函数
|
// 回复类型映射
|
||||||
const handleSearch = (value: string) => {
|
const getReplyTypeText = (replyType: string) => {
|
||||||
fetchKeywords({ keyword: value });
|
switch (replyType) {
|
||||||
};
|
case "text":
|
||||||
|
return "文本回复";
|
||||||
|
case "template":
|
||||||
|
return "模板回复";
|
||||||
|
default:
|
||||||
|
return "未知类型";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 组件挂载时获取数据
|
// 回复类型颜色
|
||||||
useEffect(() => {
|
const getReplyTypeColor = (replyType: string) => {
|
||||||
fetchKeywords();
|
switch (replyType) {
|
||||||
}, []);
|
case "text":
|
||||||
|
return "#1890ff";
|
||||||
|
case "template":
|
||||||
|
return "#722ed1";
|
||||||
|
default:
|
||||||
|
return "#8c8c8c";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// 获取关键词列表
|
||||||
<div className={styles.keywordContent}>
|
const fetchKeywords = async (params?: KeywordListParams) => {
|
||||||
<div className={styles.searchSection}>
|
try {
|
||||||
<Search
|
setLoading(true);
|
||||||
placeholder="搜索关键词..."
|
const response = await getKeywordList(params || {});
|
||||||
value={searchValue}
|
if (response) {
|
||||||
onChange={e => setSearchValue(e.target.value)}
|
setKeywordsList(response.list || []);
|
||||||
onSearch={handleSearch}
|
} else {
|
||||||
style={{ width: 300 }}
|
setKeywordsList([]);
|
||||||
prefix={<SearchOutlined />}
|
message.error(response?.message || "获取关键词列表失败");
|
||||||
/>
|
}
|
||||||
<Button icon={<FilterOutlined />}>筛选</Button>
|
} catch (error) {
|
||||||
</div>
|
console.error("获取关键词列表失败:", error);
|
||||||
|
setKeywordsList([]);
|
||||||
|
message.error("获取关键词列表失败");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
<div className={styles.keywordList}>
|
// 暴露方法给父组件
|
||||||
{loading ? (
|
useImperativeHandle(ref, () => ({
|
||||||
<div className={styles.loading}>加载中...</div>
|
fetchKeywords,
|
||||||
) : filteredKeywords.length === 0 ? (
|
}));
|
||||||
<div className={styles.empty}>暂无关键词数据</div>
|
|
||||||
) : (
|
// 关键词管理相关函数
|
||||||
filteredKeywords.map(item => (
|
const handleToggleKeyword = async (id: string) => {
|
||||||
<div key={item.id} className={styles.keywordItem}>
|
try {
|
||||||
<div className={styles.itemContent}>
|
const response = await setKeywordStatus({ id });
|
||||||
<div className={styles.title}>{item.title}</div>
|
if (response) {
|
||||||
<div className={styles.tags}>
|
setKeywordsList(prev =>
|
||||||
<Tag className={styles.matchTag}>{item.matchType}</Tag>
|
prev.map(item =>
|
||||||
<Tag className={styles.priorityTag}>
|
item.id === id ? { ...item, enabled: !item.enabled } : item,
|
||||||
优先级{item.priority}
|
),
|
||||||
|
);
|
||||||
|
message.success("状态更新成功");
|
||||||
|
} else {
|
||||||
|
message.error(response?.message || "状态更新失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("状态更新失败:", error);
|
||||||
|
message.error("状态更新失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditKeyword = (id: string) => {
|
||||||
|
setEditingKeywordId(id);
|
||||||
|
setEditModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑弹窗成功回调
|
||||||
|
const handleEditSuccess = () => {
|
||||||
|
fetchKeywords(); // 重新获取数据
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteKeyword = async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response = await deleteKeyword(id);
|
||||||
|
if (response) {
|
||||||
|
setKeywordsList(prev => prev.filter(item => item.id !== id));
|
||||||
|
message.success("删除成功");
|
||||||
|
} else {
|
||||||
|
message.error(response?.message || "删除失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("删除失败:", error);
|
||||||
|
message.error("删除失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索和筛选功能
|
||||||
|
const filteredKeywords = keywordsList.filter(item => {
|
||||||
|
if (!searchValue) return true;
|
||||||
|
return (
|
||||||
|
item.title.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||||
|
item.keywords.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||||
|
item.content.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索处理函数
|
||||||
|
const handleSearch = (value: string) => {
|
||||||
|
fetchKeywords({ keyword: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件挂载时获取数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchKeywords();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.keywordContent}>
|
||||||
|
<div className={styles.searchSection}>
|
||||||
|
<Search
|
||||||
|
placeholder="搜索关键词..."
|
||||||
|
value={searchValue}
|
||||||
|
onChange={e => setSearchValue(e.target.value)}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
style={{ width: 300 }}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
/>
|
||||||
|
<Button icon={<FilterOutlined />}>筛选</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.keywordList}>
|
||||||
|
{loading ? (
|
||||||
|
<div className={styles.loading}>加载中...</div>
|
||||||
|
) : filteredKeywords.length === 0 ? (
|
||||||
|
<div className={styles.empty}>暂无关键词数据</div>
|
||||||
|
) : (
|
||||||
|
filteredKeywords.map(item => (
|
||||||
|
<div key={item.id} className={styles.keywordItem}>
|
||||||
|
<div className={styles.itemContent}>
|
||||||
|
<div className={styles.title}>{item.title}</div>
|
||||||
|
<div className={styles.tags}>
|
||||||
|
<Tag className={styles.matchTag}>{item.matchType}</Tag>
|
||||||
|
<Tag className={styles.priorityTag}>
|
||||||
|
优先级{item.priority}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<div className={styles.description}>{item.content}</div>
|
||||||
|
<Tag
|
||||||
|
color={getReplyTypeColor(item.replyType)}
|
||||||
|
className={styles.replyTypeTag}
|
||||||
|
>
|
||||||
|
{getReplyTypeText(item.replyType)}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.description}>{item.content}</div>
|
<div className={styles.itemActions}>
|
||||||
<Tag
|
<Switch
|
||||||
color={getReplyTypeColor(item.replyType)}
|
checked={item.enabled}
|
||||||
className={styles.replyTypeTag}
|
onChange={() => handleToggleKeyword(item.id)}
|
||||||
>
|
className={styles.toggleSwitch}
|
||||||
{getReplyTypeText(item.replyType)}
|
/>
|
||||||
</Tag>
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<FormOutlined className={styles.editIcon} />}
|
||||||
|
onClick={() => handleEditKeyword(item.id)}
|
||||||
|
className={styles.actionBtn}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined className={styles.deleteIcon} />}
|
||||||
|
onClick={() => handleDeleteKeyword(item.id)}
|
||||||
|
className={styles.actionBtn}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.itemActions}>
|
))
|
||||||
<Switch
|
)}
|
||||||
checked={item.enabled}
|
</div>
|
||||||
onChange={() => handleToggleKeyword(item.id)}
|
|
||||||
className={styles.toggleSwitch}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={<FormOutlined className={styles.editIcon} />}
|
|
||||||
onClick={() => handleEditKeyword(item.id)}
|
|
||||||
className={styles.actionBtn}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={<DeleteOutlined className={styles.deleteIcon} />}
|
|
||||||
onClick={() => handleDeleteKeyword(item.id)}
|
|
||||||
className={styles.actionBtn}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 编辑弹窗 */}
|
{/* 编辑弹窗 */}
|
||||||
<KeywordModal
|
<KeywordModal
|
||||||
visible={editModalVisible}
|
visible={editModalVisible}
|
||||||
mode="edit"
|
mode="edit"
|
||||||
keywordId={editingKeywordId}
|
keywordId={editingKeywordId}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setEditModalVisible(false);
|
setEditModalVisible(false);
|
||||||
setEditingKeywordId(null);
|
setEditingKeywordId(null);
|
||||||
}}
|
}}
|
||||||
onSuccess={handleEditSuccess}
|
onSuccess={handleEditSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
KeywordManagement.displayName = "KeywordManagement";
|
||||||
|
|
||||||
export default KeywordManagement;
|
export default KeywordManagement;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { Modal, Form, Input, Button, message, Select } from "antd";
|
import { Modal, Form, Input, Button, message, Select } from "antd";
|
||||||
import {
|
import {
|
||||||
addKeyword,
|
addKeyword,
|
||||||
@@ -30,36 +30,42 @@ const KeywordModal: React.FC<KeywordModalProps> = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 获取关键词详情
|
// 获取关键词详情
|
||||||
const fetchKeywordDetails = async (id: string) => {
|
const fetchKeywordDetails = useCallback(
|
||||||
try {
|
async (id: string) => {
|
||||||
const response = await getKeywordDetails(id);
|
try {
|
||||||
if (response) {
|
const response = await getKeywordDetails(id);
|
||||||
const keyword = response;
|
if (response) {
|
||||||
form.setFieldsValue({
|
const keyword = response;
|
||||||
title: keyword.title,
|
form.setFieldsValue({
|
||||||
keywords: keyword.keywords,
|
title: keyword.title,
|
||||||
content: keyword.content,
|
keywords: keyword.keywords,
|
||||||
matchType: keyword.matchType,
|
content: keyword.content,
|
||||||
priority: keyword.priority,
|
matchType: keyword.matchType,
|
||||||
replyType: keyword.replyType,
|
priority: keyword.priority,
|
||||||
status: keyword.status,
|
replyType: keyword.replyType,
|
||||||
});
|
status: keyword.status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取关键词详情失败:", error);
|
||||||
|
message.error("获取关键词详情失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.error("获取关键词详情失败:", error);
|
[form],
|
||||||
message.error("获取关键词详情失败");
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 当弹窗打开且为编辑模式时,获取详情
|
// 当弹窗打开时处理数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && mode === "edit" && keywordId) {
|
if (visible) {
|
||||||
fetchKeywordDetails(keywordId);
|
if (mode === "edit" && keywordId) {
|
||||||
} else if (visible && mode === "add") {
|
// 编辑模式:获取详情
|
||||||
// 添加模式时重置表单
|
fetchKeywordDetails(keywordId);
|
||||||
form.resetFields();
|
} else if (mode === "add") {
|
||||||
|
// 添加模式:重置表单
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [visible, mode, keywordId]);
|
}, [visible, mode, keywordId, fetchKeywordDetails, form]);
|
||||||
|
|
||||||
const handleSubmit = async (values: any) => {
|
const handleSubmit = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ const ContentManagement: React.FC = () => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [keywordModalVisible, setKeywordModalVisible] = useState(false);
|
const [keywordModalVisible, setKeywordModalVisible] = useState(false);
|
||||||
|
|
||||||
// 引用素材管理组件
|
// 引用管理组件
|
||||||
const materialManagementRef = useRef<any>(null);
|
const materialManagementRef = useRef<any>(null);
|
||||||
|
const keywordManagementRef = useRef<any>(null);
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: "material", label: "素材资源库" },
|
{ key: "material", label: "素材资源库" },
|
||||||
@@ -47,18 +48,28 @@ const ContentManagement: React.FC = () => {
|
|||||||
if (materialManagementRef.current?.fetchMaterials) {
|
if (materialManagementRef.current?.fetchMaterials) {
|
||||||
materialManagementRef.current.fetchMaterials();
|
materialManagementRef.current.fetchMaterials();
|
||||||
}
|
}
|
||||||
|
// 刷新关键词列表
|
||||||
|
if (keywordManagementRef.current?.fetchKeywords) {
|
||||||
|
keywordManagementRef.current.fetchKeywords();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTabContent = () => {
|
const renderTabContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case "material":
|
case "material":
|
||||||
return <MaterialManagement ref={materialManagementRef} />;
|
return (
|
||||||
|
<MaterialManagement ref={materialManagementRef} {...({} as any)} />
|
||||||
|
);
|
||||||
case "sensitive":
|
case "sensitive":
|
||||||
return <SensitiveWordManagement />;
|
return <SensitiveWordManagement />;
|
||||||
case "keyword":
|
case "keyword":
|
||||||
return <KeywordManagement />;
|
return (
|
||||||
|
<KeywordManagement ref={keywordManagementRef} {...({} as any)} />
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <MaterialManagement ref={materialManagementRef} />;
|
return (
|
||||||
|
<MaterialManagement ref={materialManagementRef} {...({} as any)} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user