更新敏感词管理模块:将敏感词管理组件转换为使用forwardRef,增强与父组件的交互能力;修复敏感词状态更新接口的请求方法;优化删除功能,增加确认删除的弹窗;改进敏感词详情获取逻辑,提升用户体验。

This commit is contained in:
超级老白兔
2025-09-28 16:36:17 +08:00
parent 9fa09bb743
commit 0ae2265c50
5 changed files with 277 additions and 241 deletions

View File

@@ -118,7 +118,7 @@ export function updateSensitiveWord(data: SensitiveWordUpdateRequest) {
// 违禁词管理-修改状态 // 违禁词管理-修改状态
export function setSensitiveWordStatus(data: SensitiveWordSetStatusRequest) { export function setSensitiveWordStatus(data: SensitiveWordSetStatusRequest) {
return request("/v1/kefu/content/sensitiveWord/setStatus", data, "POST"); return request("/v1/kefu/content/sensitiveWord/setStatus", data, "GET");
} }
// 关键词回复管理相关接口 // 关键词回复管理相关接口

View File

@@ -4,7 +4,7 @@ import React, {
forwardRef, forwardRef,
useImperativeHandle, useImperativeHandle,
} from "react"; } from "react";
import { Button, Input, Card, message, Modal } from "antd"; import { Button, Input, Card, message, Popconfirm } from "antd";
import { import {
SearchOutlined, SearchOutlined,
FilterOutlined, FilterOutlined,
@@ -91,22 +91,13 @@ const MaterialManagement = forwardRef<any, Record<string, never>>(
// 素材管理相关函数 // 素材管理相关函数
const handleDeleteMaterial = async (id: number) => { const handleDeleteMaterial = async (id: number) => {
Modal.confirm({ try {
title: "确认删除", await deleteMaterial(id.toString());
content: "确定要删除这个素材吗?删除后无法恢复。", setMaterialsList(prev => prev.filter(item => item.id !== id));
okText: "确定", message.success("删除成功");
cancelText: "取消", } catch (error) {
okType: "danger", message.error("删除失败");
onOk: async () => { }
try {
await deleteMaterial(id.toString());
setMaterialsList(prev => prev.filter(item => item.id !== id));
message.success("删除成功");
} catch (error) {
message.error("删除失败");
}
},
});
}; };
// 编辑素材 // 编辑素材
@@ -167,18 +158,24 @@ const MaterialManagement = forwardRef<any, Record<string, never>>(
> >
</Button>, </Button>,
<Button <Popconfirm
key="delete" key="delete"
type="text" title="确认删除"
danger description="确定要删除这个素材吗?删除后无法恢复。"
icon={<DeleteOutlined />} onConfirm={() => handleDeleteMaterial(item.id)}
onClick={e => { okText="确定"
e.stopPropagation(); cancelText="取消"
handleDeleteMaterial(item.id); okType="danger"
}}
> >
<Button
</Button>, type="text"
danger
icon={<DeleteOutlined />}
onClick={e => e.stopPropagation()}
>
</Button>
</Popconfirm>,
]} ]}
> >
<div <div

View File

@@ -1,5 +1,10 @@
import React, { useState, useEffect } from "react"; import React, {
import { Button, Input, Tag, Switch, message } from "antd"; useState,
useEffect,
forwardRef,
useImperativeHandle,
} from "react";
import { Button, Input, Tag, Switch, message, Popconfirm } from "antd";
import { import {
SearchOutlined, SearchOutlined,
FilterOutlined, FilterOutlined,
@@ -27,206 +32,219 @@ interface SensitiveWordItem {
enabled: boolean; enabled: boolean;
} }
const SensitiveWordManagement: React.FC = () => { const SensitiveWordManagement = forwardRef<any, Record<string, never>>(
const [searchValue, setSearchValue] = useState<string>(""); (props, ref) => {
const [sensitiveWordsList, setSensitiveWordsList] = useState< const [searchValue, setSearchValue] = useState<string>("");
SensitiveWordItem[] const [sensitiveWordsList, setSensitiveWordsList] = useState<
>([]); SensitiveWordItem[]
const [loading, setLoading] = useState<boolean>(false); >([]);
const [editModalVisible, setEditModalVisible] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [editingSensitiveWordId, setEditingSensitiveWordId] = useState< const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
string | null const [editingSensitiveWordId, setEditingSensitiveWordId] = useState<
>(null); string | null
>(null);
const getTagColor = (tag: string) => { const getTagColor = (tag: string) => {
switch (tag) { switch (tag) {
case "政治": case "政治":
return "#ff4d4f"; return "#ff4d4f";
case "色情": case "色情":
return "#ff4d4f"; return "#ff4d4f";
case "暴力": case "暴力":
return "#ff4d4f"; return "#ff4d4f";
default: default:
return "#ff4d4f"; return "#ff4d4f";
} }
}; };
// 操作类型映射 // 操作类型映射
const getOperationText = (operation: string) => { const getOperationText = (operation: string) => {
switch (operation) { switch (operation) {
case "0": case "0":
return "不操作"; return "不操作";
case "1": case "1":
return "替换"; return "替换";
case "2": case "2":
return "删除"; return "删除";
case "3": case "3":
return "警告"; return "警告";
case "4": case "4":
return "禁止发送"; return "禁止发送";
default: default:
return "未知操作"; return "未知操作";
} }
}; };
// 获取敏感词列表 // 获取敏感词列表
const fetchSensitiveWords = async (params?: SensitiveWordListParams) => { const fetchSensitiveWords = async (params?: SensitiveWordListParams) => {
try { try {
setLoading(true); setLoading(true);
const response = await getSensitiveWordList(params || {}); const response = await getSensitiveWordList(params || {});
if (response) { if (response) {
setSensitiveWordsList(response.list || []); setSensitiveWordsList(response.list || []);
} else { } else {
setSensitiveWordsList([]);
message.error(response?.message || "获取敏感词列表失败");
}
} catch (error) {
console.error("获取敏感词列表失败:", error);
setSensitiveWordsList([]); setSensitiveWordsList([]);
message.error(response?.message || "获取敏感词列表失败"); message.error("获取敏感词列表失败");
} finally {
setLoading(false);
} }
} catch (error) { };
console.error("获取敏感词列表失败:", error);
setSensitiveWordsList([]);
message.error("获取敏感词列表失败");
} finally {
setLoading(false);
}
};
// 敏感词管理相关函数 // 暴露方法给父组件
const handleToggleSensitiveWord = async (id: string) => { useImperativeHandle(ref, () => ({
try { fetchSensitiveWords,
const response = await setSensitiveWordStatus({ id }); }));
if (response) {
setSensitiveWordsList(prev => // 敏感词管理相关函数
prev.map(item => const handleToggleSensitiveWord = async (id: string) => {
item.id === id ? { ...item, enabled: !item.enabled } : item, try {
), const response = await setSensitiveWordStatus({ id });
); if (response) {
message.success("状态更新成功"); setSensitiveWordsList(prev =>
} else { prev.map(item =>
message.error(response?.message || "状态更新失败"); item.id === id ? { ...item, enabled: !item.enabled } : item,
),
);
message.success("状态更新成功");
} else {
message.error(response?.message || "状态更新失败");
}
} catch (error) {
console.error("状态更新失败:", error);
message.error("状态更新失败");
} }
} catch (error) { };
console.error("状态更新失败:", error);
message.error("状态更新失败");
}
};
const handleEditSensitiveWord = (id: string) => { const handleEditSensitiveWord = (id: string) => {
setEditingSensitiveWordId(id); setEditingSensitiveWordId(id);
setEditModalVisible(true); setEditModalVisible(true);
}; };
// 编辑弹窗成功回调 // 编辑弹窗成功回调
const handleEditSuccess = () => { const handleEditSuccess = () => {
fetchSensitiveWords(); // 重新获取数据 fetchSensitiveWords(); // 重新获取数据
}; };
const handleDeleteSensitiveWord = async (id: string) => { const handleDeleteSensitiveWord = async (id: string) => {
try { try {
const response = await deleteSensitiveWord(id); await deleteSensitiveWord(id);
if (response) {
setSensitiveWordsList(prev => prev.filter(item => item.id !== id)); setSensitiveWordsList(prev => prev.filter(item => item.id !== id));
message.success("删除成功"); message.success("删除成功");
} else { } catch (error) {
message.error(response?.message || "删除失败"); console.error("删除失败:", error);
message.error("删除失败");
} }
} catch (error) { };
console.error("删除失败:", error);
message.error("删除失败"); // 搜索和筛选功能
} const filteredSensitiveWords = sensitiveWordsList.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) => {
fetchSensitiveWords({ keyword: value });
};
// 组件挂载时获取数据
useEffect(() => {
fetchSensitiveWords();
}, []);
// 搜索和筛选功能
const filteredSensitiveWords = sensitiveWordsList.filter(item => {
if (!searchValue) return true;
return ( return (
item.title.toLowerCase().includes(searchValue.toLowerCase()) || <div className={styles.sensitiveContent}>
item.keywords.toLowerCase().includes(searchValue.toLowerCase()) || <div className={styles.searchSection}>
item.content.toLowerCase().includes(searchValue.toLowerCase()) <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.sensitiveList}>
const handleSearch = (value: string) => { {loading ? (
fetchSensitiveWords({ keyword: value }); <div className={styles.loading}>...</div>
}; ) : filteredSensitiveWords.length === 0 ? (
<div className={styles.empty}></div>
// 组件挂载时获取数据 ) : (
useEffect(() => { filteredSensitiveWords.map(item => (
fetchSensitiveWords(); <div key={item.id} className={styles.sensitiveItem}>
}, []); <div className={styles.itemContent}>
<div className={styles.categoryName}>{item.title}</div>
return ( <Tag
<div className={styles.sensitiveContent}> color={getTagColor(item.keywords)}
<div className={styles.searchSection}> className={styles.sensitiveTag}
<Search >
placeholder="搜索敏感词..." {item.keywords}
value={searchValue} </Tag>
onChange={e => setSearchValue(e.target.value)} <div className={styles.actionText}>
onSearch={handleSearch} {getOperationText(item.operation)}
style={{ width: 300 }} </div>
prefix={<SearchOutlined />} </div>
/> <div className={styles.itemActions}>
<Button icon={<FilterOutlined />}></Button> <Switch
</div> checked={item.enabled}
onChange={() => handleToggleSensitiveWord(item.id)}
<div className={styles.sensitiveList}> className={styles.toggleSwitch}
{loading ? ( />
<div className={styles.loading}>...</div> <Button
) : filteredSensitiveWords.length === 0 ? ( type="text"
<div className={styles.empty}></div> size="small"
) : ( icon={<FormOutlined className={styles.editIcon} />}
filteredSensitiveWords.map(item => ( onClick={() => handleEditSensitiveWord(item.id)}
<div key={item.id} className={styles.sensitiveItem}> className={styles.actionBtn}
<div className={styles.itemContent}> />
<div className={styles.categoryName}>{item.title}</div> <Popconfirm
<Tag title="确认删除"
color={getTagColor(item.keywords)} description="确定要删除这个敏感词吗?删除后无法恢复。"
className={styles.sensitiveTag} onConfirm={() => handleDeleteSensitiveWord(item.id)}
> okText="确定"
{item.keywords} cancelText="取消"
</Tag> okType="danger"
<div className={styles.actionText}> >
{getOperationText(item.operation)} <Button
type="text"
size="small"
icon={<DeleteOutlined className={styles.deleteIcon} />}
className={styles.actionBtn}
/>
</Popconfirm>
</div> </div>
</div> </div>
<div className={styles.itemActions}> ))
<Switch )}
checked={item.enabled} </div>
onChange={() => handleToggleSensitiveWord(item.id)}
className={styles.toggleSwitch}
/>
<Button
type="text"
size="small"
icon={<FormOutlined className={styles.editIcon} />}
onClick={() => handleEditSensitiveWord(item.id)}
className={styles.actionBtn}
/>
<Button
type="text"
size="small"
icon={<DeleteOutlined className={styles.deleteIcon} />}
onClick={() => handleDeleteSensitiveWord(item.id)}
className={styles.actionBtn}
/>
</div>
</div>
))
)}
</div>
{/* 编辑弹窗 */} {/* 编辑弹窗 */}
<SensitiveWordModal <SensitiveWordModal
visible={editModalVisible} visible={editModalVisible}
mode="edit" mode="edit"
sensitiveWordId={editingSensitiveWordId} sensitiveWordId={editingSensitiveWordId}
onCancel={() => { onCancel={() => {
setEditModalVisible(false); setEditModalVisible(false);
setEditingSensitiveWordId(null); setEditingSensitiveWordId(null);
}} }}
onSuccess={handleEditSuccess} onSuccess={handleEditSuccess}
/> />
</div> </div>
); );
}; },
);
SensitiveWordManagement.displayName = "SensitiveWordManagement";
export default SensitiveWordManagement; export default SensitiveWordManagement;

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 {
addSensitiveWord, addSensitiveWord,
@@ -30,34 +30,40 @@ const SensitiveWordModal: React.FC<SensitiveWordModalProps> = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// 获取敏感词详情 // 获取敏感词详情
const fetchSensitiveWordDetails = async (id: string) => { const fetchSensitiveWordDetails = useCallback(
try { async (id: string) => {
const response = await getSensitiveWordDetails(id); try {
if (response) { const response = await getSensitiveWordDetails(id);
const sensitiveWord = response; if (response) {
form.setFieldsValue({ const sensitiveWord = response;
title: sensitiveWord.title, form.setFieldsValue({
keywords: sensitiveWord.keywords, title: sensitiveWord.title,
content: sensitiveWord.content, keywords: sensitiveWord.keywords,
operation: sensitiveWord.operation, content: sensitiveWord.content,
status: sensitiveWord.status, operation: sensitiveWord.operation,
}); status: sensitiveWord.status,
});
}
} catch (error) {
console.error("获取敏感词详情失败:", error);
message.error("获取敏感词详情失败");
} }
} catch (error) { },
console.error("获取敏感词详情失败:", error); [form],
message.error("获取敏感词详情失败"); );
}
};
// 当弹窗打开且为编辑模式时,获取详情 // 当弹窗打开时处理数据
useEffect(() => { useEffect(() => {
if (visible && mode === "edit" && sensitiveWordId) { if (visible) {
fetchSensitiveWordDetails(sensitiveWordId); if (mode === "edit" && sensitiveWordId) {
} else if (visible && mode === "add") { // 编辑模式:获取详情
// 添加模式时重置表单 fetchSensitiveWordDetails(sensitiveWordId);
form.resetFields(); } else if (mode === "add") {
// 添加模式:重置表单
form.resetFields();
}
} }
}, [visible, mode, sensitiveWordId]); }, [visible, mode, sensitiveWordId, fetchSensitiveWordDetails, form]);
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
try { try {

View File

@@ -22,6 +22,7 @@ const ContentManagement: React.FC = () => {
// 引用管理组件 // 引用管理组件
const materialManagementRef = useRef<any>(null); const materialManagementRef = useRef<any>(null);
const keywordManagementRef = useRef<any>(null); const keywordManagementRef = useRef<any>(null);
const sensitiveWordManagementRef = useRef<any>(null);
const tabs = [ const tabs = [
{ key: "material", label: "素材资源库" }, { key: "material", label: "素材资源库" },
@@ -44,12 +45,20 @@ const ContentManagement: React.FC = () => {
// 弹窗成功回调 // 弹窗成功回调
const handleModalSuccess = () => { const handleModalSuccess = () => {
console.log("handleModalSuccess");
// 刷新素材列表 // 刷新素材列表
if (materialManagementRef.current?.fetchMaterials) { if (materialManagementRef.current?.fetchMaterials) {
console.log("刷新素材列表");
materialManagementRef.current.fetchMaterials(); materialManagementRef.current.fetchMaterials();
} }
// 刷新敏感词列表
if (sensitiveWordManagementRef.current?.fetchSensitiveWords) {
console.log("刷新敏感词列表");
sensitiveWordManagementRef.current.fetchSensitiveWords();
}
// 刷新关键词列表 // 刷新关键词列表
if (keywordManagementRef.current?.fetchKeywords) { if (keywordManagementRef.current?.fetchKeywords) {
console.log("刷新关键词列表");
keywordManagementRef.current.fetchKeywords(); keywordManagementRef.current.fetchKeywords();
} }
}; };
@@ -61,7 +70,12 @@ const ContentManagement: React.FC = () => {
<MaterialManagement ref={materialManagementRef} {...({} as any)} /> <MaterialManagement ref={materialManagementRef} {...({} as any)} />
); );
case "sensitive": case "sensitive":
return <SensitiveWordManagement />; return (
<SensitiveWordManagement
ref={sensitiveWordManagementRef}
{...({} as any)}
/>
);
case "keyword": case "keyword":
return ( return (
<KeywordManagement ref={keywordManagementRef} {...({} as any)} /> <KeywordManagement ref={keywordManagementRef} {...({} as any)} />
@@ -89,12 +103,12 @@ const ContentManagement: React.FC = () => {
> >
</Button> </Button>
<Button icon={<PlusOutlined />} onClick={handleAddKeyword}>
</Button>
<Button icon={<PlusOutlined />} onClick={handleAddSensitiveWord}> <Button icon={<PlusOutlined />} onClick={handleAddSensitiveWord}>
</Button> </Button>
<Button icon={<PlusOutlined />} onClick={handleAddKeyword}>
</Button>
</div> </div>
} }
/> />
@@ -136,6 +150,7 @@ const ContentManagement: React.FC = () => {
<KeywordModal <KeywordModal
visible={keywordModalVisible} visible={keywordModalVisible}
mode="add" mode="add"
keywordId={null}
onCancel={() => setKeywordModalVisible(false)} onCancel={() => setKeywordModalVisible(false)}
onSuccess={handleModalSuccess} onSuccess={handleModalSuccess}
/> />