重构关键词管理模块:将关键词管理组件转换为使用forwardRef,增强与父组件的交互能力;优化关键词列表的获取和处理逻辑,提升用户体验。

This commit is contained in:
超级老白兔
2025-09-28 16:04:32 +08:00
parent c6639a8259
commit c90628a71f
3 changed files with 250 additions and 217 deletions

View File

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

View File

@@ -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 {

View File

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