import { useEffect, useRef, useState } from 'react'; import { adminApi, type AdminChannel } from '@/api/adminApi'; import { channelWordsApi, type ChannelWord, type ChannelWordForm, type ChannelWordQuery, } from '@/api/admin/channel-sensitive-words.api'; import { Button, Input, Modal, Pagination, QueryPanel, Select, Table, Tag, type TableColumn } from '@/components/ui'; import { formatDateTime } from '@/utils/dateTime'; const emptyForm: ChannelWordForm = { channelId: '', word: '', status: 'active', remark: '' }; const initialQuery: ChannelWordQuery = { channelId: '', keyword: '', status: 'all', page: 1, pageSize: 25 }; const statuses = [ { label: '启用', value: 'active' }, { label: '停用', value: 'inactive' }, ]; export function ChannelSensitiveWordsPanel() { const [channels, setChannels] = useState([]); const [filters, setFilters] = useState(initialQuery); const [query, setQuery] = useState(initialQuery); const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [channelError, setChannelError] = useState(''); const [reload, setReload] = useState(0); const [editor, setEditor] = useState<{ item?: ChannelWord; initial: ChannelWordForm } | null>(null); const [form, setForm] = useState(emptyForm); const [formError, setFormError] = useState(''); const [saving, setSaving] = useState(false); const [removing, setRemoving] = useState(null); const [removeError, setRemoveError] = useState(''); const busy = useRef(false); useEffect(() => { let alive = true; adminApi .listChannels() .then((data) => { if (alive) { setChannels(data); setChannelError(''); } }) .catch((failure: Error) => { if (alive) setChannelError(failure.message || '通道加载失败'); }); return () => { alive = false; }; }, [reload]); useEffect(() => { let alive = true; setLoading(true); setError(''); channelWordsApi .list(query) .then((data) => { if (!alive) return; if (query.page > 1 && !data.items.length) { setQuery({ ...query, page: Math.max(1, Math.ceil(data.total / query.pageSize)) }); return; } setItems(data.items); setTotal(data.total); }) .catch((failure: Error) => { if (alive) { setError(failure.message || '通道敏感词加载失败'); setItems([]); setTotal(0); } }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [query, reload]); const channelOptions = channels .filter((channel) => channel.status !== 'deleted') .map((channel) => ({ value: channel.id, label: `${channel.name}${channel.status === 'active' ? '' : '(已停用)'}`, })); if (editor?.item && !channelOptions.some((option) => option.value === editor.item!.channelId)) channelOptions.push({ value: editor.item.channelId, label: `${editor.item.channel.name}(历史通道)` }); function edit(item?: ChannelWord) { const initial = item ? { channelId: item.channelId, word: item.word, status: item.status, remark: item.remark } : { ...emptyForm }; setEditor({ item, initial }); setForm(initial); setFormError(''); } async function save() { if (busy.current || !editor) return; if (!form.channelId || !form.word.trim()) { setFormError('请选择通道并填写敏感词'); return; } busy.current = true; setSaving(true); setFormError(''); try { await channelWordsApi.save(form, editor.item); setEditor(null); setReload((value) => value + 1); } catch (failure) { setFormError(failure instanceof Error ? failure.message : '保存失败'); } finally { busy.current = false; setSaving(false); } } async function toggle(item: ChannelWord) { if (busy.current) return; busy.current = true; setSaving(true); setError(''); try { await channelWordsApi.save( { channelId: item.channelId, word: item.word, remark: item.remark, status: item.status === 'active' ? 'inactive' : 'active', }, item, ); setReload((value) => value + 1); } catch (failure) { setError(failure instanceof Error ? failure.message : '操作失败'); } finally { busy.current = false; setSaving(false); } } async function remove() { if (busy.current || !removing) return; busy.current = true; setSaving(true); setRemoveError(''); try { await channelWordsApi.remove(removing); setRemoving(null); setReload((value) => value + 1); } catch (failure) { setRemoveError(failure instanceof Error ? failure.message : '删除失败'); } finally { busy.current = false; setSaving(false); } } const columns: Array> = [ { key: 'channel', title: '通道', render: (item) => `${item.channel.name}${item.channel.status === 'active' ? '' : '(已停用或删除)'}`, }, { key: 'word', title: '敏感词', render: (item) => item.word }, { key: 'status', title: '状态', render: (item) => ( {item.status === 'active' ? '启用' : '停用'} ), }, { key: 'remark', title: '备注', render: (item) => item.remark || '-' }, { key: 'updatedAt', title: '更新时间', render: (item) => formatDateTime(item.updatedAt) }, { key: 'actions', title: '操作', render: (item) => (
), }, ]; return (

命中启用词的短信不走对应通道。已完成选路的消息不受后续配置变更影响。

setFilters({ ...filters, keyword: event.target.value })} /> setForm({ ...form, channelId: event.target.value })} /> setForm({ ...form, word: event.target.value })} /> setForm({ ...form, remark: event.target.value })} />
{ if (!saving) setRemoving(null); }} footer={ <> } >

确认删除 {removing?.channel.name} 的敏感词“{removing?.word}”?历史筛选记录会保留。

{removeError ? (

{removeError}

) : null}
); }