重构关键词管理模块:将关键词管理组件转换为使用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 {
SearchOutlined,
@@ -29,199 +34,210 @@ interface KeywordItem {
enabled: boolean;
}
const KeywordManagement: React.FC = () => {
const [searchValue, setSearchValue] = useState<string>("");
const [keywordsList, setKeywordsList] = useState<KeywordItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
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 KeywordManagement = forwardRef<any, Record<string, never>>(
(props, ref) => {
const [searchValue, setSearchValue] = useState<string>("");
const [keywordsList, setKeywordsList] = useState<KeywordItem[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
const [editingKeywordId, setEditingKeywordId] = useState<string | null>(
null,
);
});
// 搜索处理函数
const handleSearch = (value: string) => {
fetchKeywords({ keyword: value });
};
// 回复类型映射
const getReplyTypeText = (replyType: string) => {
switch (replyType) {
case "text":
return "文本回复";
case "template":
return "模板回复";
default:
return "未知类型";
}
};
// 组件挂载时获取数据
useEffect(() => {
fetchKeywords();
}, []);
// 回复类型颜色
const getReplyTypeColor = (replyType: string) => {
switch (replyType) {
case "text":
return "#1890ff";
case "template":
return "#722ed1";
default:
return "#8c8c8c";
}
};
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>
// 获取关键词列表
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);
}
};
<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}
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
fetchKeywords,
}));
// 关键词管理相关函数
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) => {
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>
</div>
<div className={styles.description}>{item.content}</div>
<Tag
color={getReplyTypeColor(item.replyType)}
className={styles.replyTypeTag}
>
{getReplyTypeText(item.replyType)}
</Tag>
<div className={styles.itemActions}>
<Switch
checked={item.enabled}
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 className={styles.itemActions}>
<Switch
checked={item.enabled}
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>
))
)}
</div>
{/* 编辑弹窗 */}
<KeywordModal
visible={editModalVisible}
mode="edit"
keywordId={editingKeywordId}
onCancel={() => {
setEditModalVisible(false);
setEditingKeywordId(null);
}}
onSuccess={handleEditSuccess}
/>
</div>
);
};
{/* 编辑弹窗 */}
<KeywordModal
visible={editModalVisible}
mode="edit"
keywordId={editingKeywordId}
onCancel={() => {
setEditModalVisible(false);
setEditingKeywordId(null);
}}
onSuccess={handleEditSuccess}
/>
</div>
);
},
);
KeywordManagement.displayName = "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 {
addKeyword,
@@ -30,36 +30,42 @@ const KeywordModal: React.FC<KeywordModalProps> = ({
const [loading, setLoading] = useState(false);
// 获取关键词详情
const fetchKeywordDetails = async (id: string) => {
try {
const response = await getKeywordDetails(id);
if (response) {
const keyword = response;
form.setFieldsValue({
title: keyword.title,
keywords: keyword.keywords,
content: keyword.content,
matchType: keyword.matchType,
priority: keyword.priority,
replyType: keyword.replyType,
status: keyword.status,
});
const fetchKeywordDetails = useCallback(
async (id: string) => {
try {
const response = await getKeywordDetails(id);
if (response) {
const keyword = response;
form.setFieldsValue({
title: keyword.title,
keywords: keyword.keywords,
content: keyword.content,
matchType: keyword.matchType,
priority: keyword.priority,
replyType: keyword.replyType,
status: keyword.status,
});
}
} catch (error) {
console.error("获取关键词详情失败:", error);
message.error("获取关键词详情失败");
}
} catch (error) {
console.error("获取关键词详情失败:", error);
message.error("获取关键词详情失败");
}
};
},
[form],
);
// 当弹窗打开且为编辑模式时,获取详情
// 当弹窗打开时处理数据
useEffect(() => {
if (visible && mode === "edit" && keywordId) {
fetchKeywordDetails(keywordId);
} else if (visible && mode === "add") {
// 添加模式时重置表单
form.resetFields();
if (visible) {
if (mode === "edit" && keywordId) {
// 编辑模式:获取详情
fetchKeywordDetails(keywordId);
} else if (mode === "add") {
// 添加模式:重置表单
form.resetFields();
}
}
}, [visible, mode, keywordId]);
}, [visible, mode, keywordId, fetchKeywordDetails, form]);
const handleSubmit = async (values: any) => {
try {

View File

@@ -19,8 +19,9 @@ const ContentManagement: React.FC = () => {
useState(false);
const [keywordModalVisible, setKeywordModalVisible] = useState(false);
// 引用素材管理组件
// 引用管理组件
const materialManagementRef = useRef<any>(null);
const keywordManagementRef = useRef<any>(null);
const tabs = [
{ key: "material", label: "素材资源库" },
@@ -47,18 +48,28 @@ const ContentManagement: React.FC = () => {
if (materialManagementRef.current?.fetchMaterials) {
materialManagementRef.current.fetchMaterials();
}
// 刷新关键词列表
if (keywordManagementRef.current?.fetchKeywords) {
keywordManagementRef.current.fetchKeywords();
}
};
const renderTabContent = () => {
switch (activeTab) {
case "material":
return <MaterialManagement ref={materialManagementRef} />;
return (
<MaterialManagement ref={materialManagementRef} {...({} as any)} />
);
case "sensitive":
return <SensitiveWordManagement />;
case "keyword":
return <KeywordManagement />;
return (
<KeywordManagement ref={keywordManagementRef} {...({} as any)} />
);
default:
return <MaterialManagement ref={materialManagementRef} />;
return (
<MaterialManagement ref={materialManagementRef} {...({} as any)} />
);
}
};