import React, { useEffect, useMemo, useState } from "react"; import { App, Badge, Button, Card, Col, Form, Input, InputNumber, List, Modal, Popconfirm, Row, Select, Space, Table, Tag, Typography, } from "antd"; import PageContainer from "@/components/shared/PageContainer"; import DataListPanel from "@/components/shared/DataListPanel"; import SectionCard from "@/components/shared/SectionCard"; import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, } from "@ant-design/icons"; import { useTranslation } from "react-i18next"; import { useDict } from "../../hooks/useDict"; import { deleteHotWord, getHotWordPage, getPinyinSuggestion, saveHotWord, updateHotWord, type HotWordVO, } from "../../api/business/hotword"; import { deleteHotWordGroup, getHotWordGroupOptions, getHotWordGroupPage, saveHotWordGroup, updateHotWordGroup, type HotWordGroupVO, } from "../../api/business/hotwordGroup"; import AppPagination from "../../components/shared/AppPagination"; import "./HotWords.css"; const { Option } = Select; const { Text } = Typography; type HotWordFormValues = { word: string; pinyin?: string; category?: string; hotWordGroupId?: number; weight: number; status: number; remark?: string; }; type HotWordGroupFormValues = { groupName: string; status: number; remark?: string; }; type GroupListItem = HotWordGroupVO | { id?: undefined; groupName: string; remark?: string; hotWordCount?: number; status?: number }; const HotWords: React.FC = () => { const { message } = App.useApp(); const { t } = useTranslation(); const [form] = Form.useForm(); const [groupForm] = Form.useForm(); const { items: categories } = useDict("biz_hotword_category"); const userProfile = useMemo(() => { const profileStr = sessionStorage.getItem("userProfile"); return profileStr ? JSON.parse(profileStr) : {}; }, []); const activeTenantId = useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []); const isPlatformAdmin = userProfile.isPlatformAdmin === true; const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [current, setCurrent] = useState(1); const [size, setSize] = useState(10); const [searchWord, setSearchWord] = useState(""); const [searchCategory, setSearchCategory] = useState(undefined); const [searchGroupId, setSearchGroupId] = useState(undefined); const [modalVisible, setModalVisible] = useState(false); const [editingId, setEditingId] = useState(null); const [submitLoading, setSubmitLoading] = useState(false); const [groupOptions, setGroupOptions] = useState([]); const [groupEditorVisible, setGroupEditorVisible] = useState(false); const [groupLoading, setGroupLoading] = useState(false); const [groupSubmitLoading, setGroupSubmitLoading] = useState(false); const [groupData, setGroupData] = useState([]); const [groupTotal, setGroupTotal] = useState(0); const [groupCurrent, setGroupCurrent] = useState(1); const [groupSize, setGroupSize] = useState(8); const [groupSearchInput, setGroupSearchInput] = useState(""); const [groupSearchName, setGroupSearchName] = useState(""); const [groupSearchStatus, setGroupSearchStatus] = useState(undefined); const [editingGroupId, setEditingGroupId] = useState(null); const [selectedGroupName, setSelectedGroupName] = useState(undefined); const groupNameMap = useMemo( () => Object.fromEntries(groupOptions.map((item) => [item.id, item.groupName])) as Record, [groupOptions] ); useEffect(() => { void fetchData(); }, [current, searchCategory, searchGroupId, size]); useEffect(() => { void loadGroupOptions(); }, [isPlatformAdmin, activeTenantId]); useEffect(() => { void loadGroupPage(); }, [groupCurrent, groupSearchName, groupSearchStatus, groupSize, isPlatformAdmin, activeTenantId]); const fetchData = async () => { setLoading(true); try { const res = await getHotWordPage({ current, size, word: searchWord, category: searchCategory, hotWordGroupId: searchGroupId, tenantId: isPlatformAdmin ? activeTenantId : undefined, }); if (res.data?.data) { setData(res.data.data.records || []); setTotal(res.data.data.total || 0); } } finally { setLoading(false); } }; const loadGroupOptions = async () => { const res = await getHotWordGroupOptions(isPlatformAdmin ? activeTenantId : undefined); setGroupOptions(res.data?.data || []); }; const loadGroupPage = async () => { setGroupLoading(true); try { const res = await getHotWordGroupPage({ current: groupCurrent, size: groupSize, name: groupSearchName || undefined, status: groupSearchStatus, tenantId: isPlatformAdmin ? activeTenantId : undefined, }); setGroupData(res.data?.data?.records || []); setGroupTotal(res.data?.data?.total || 0); } finally { setGroupLoading(false); } }; const reloadGroupList = async (resetToFirstPage = false) => { await loadGroupOptions(); if (resetToFirstPage && groupCurrent !== 1) { setGroupCurrent(1); return; } await loadGroupPage(); }; const handleOpenModal = (record?: HotWordVO) => { if (record) { setEditingId(record.id); form.setFieldsValue({ word: record.word, pinyin: record.pinyinList?.[0] || "", category: record.category, hotWordGroupId: record.hotWordGroupId, weight: record.weight, status: record.status, remark: record.remark, }); } else { setEditingId(null); form.resetFields(); form.setFieldsValue({ weight: 2, status: 1, hotWordGroupId: searchGroupId }); } setModalVisible(true); }; const handleDelete = async (id: number) => { await deleteHotWord(id); message.success("删除成功"); await fetchData(); await loadGroupPage(); }; const handleSubmit = async () => { try { const values = await form.validateFields(); setSubmitLoading(true); const payload = { ...values, tenantId: isPlatformAdmin ? activeTenantId : undefined, matchStrategy: 1, pinyinList: values.pinyin ? [values.pinyin.trim()] : [], }; if (editingId) { await updateHotWord({ ...payload, id: editingId }); message.success("更新成功"); } else { await saveHotWord(payload); message.success("新增成功"); } setModalVisible(false); await Promise.all([fetchData(), loadGroupOptions(), loadGroupPage()]); } finally { setSubmitLoading(false); } }; const handleWordBlur = async (e: React.FocusEvent) => { const word = e.target.value?.trim(); if (!word || form.getFieldValue("pinyin")) { return; } try { const res = await getPinyinSuggestion(word); const firstPinyin = res.data?.data?.[0]; if (firstPinyin) { form.setFieldValue("pinyin", firstPinyin); } } catch { // handled by interceptor } }; const openGroupEditor = (record?: HotWordGroupVO, e?: React.MouseEvent) => { e?.stopPropagation(); if (record) { setEditingGroupId(record.id); groupForm.setFieldsValue({ groupName: record.groupName, status: record.status, remark: record.remark, }); } else { setEditingGroupId(null); groupForm.resetFields(); groupForm.setFieldsValue({ status: 1 }); } setGroupEditorVisible(true); }; const handleGroupSubmit = async () => { try { const values = await groupForm.validateFields(); setGroupSubmitLoading(true); if (editingGroupId) { await updateHotWordGroup({ ...values, id: editingGroupId, tenantId: isPlatformAdmin ? activeTenantId : undefined }); message.success("热词组更新成功"); } else { await saveHotWordGroup({ ...values, tenantId: isPlatformAdmin ? activeTenantId : undefined }); message.success("热词组创建成功"); } setGroupEditorVisible(false); await reloadGroupList(true); } finally { setGroupSubmitLoading(false); } }; const handleDeleteGroup = async (id: number, e?: React.MouseEvent) => { e?.stopPropagation(); await deleteHotWordGroup(id, isPlatformAdmin ? activeTenantId : undefined); message.success("热词组删除成功"); if (searchGroupId === id) { setSearchGroupId(undefined); setSelectedGroupName(undefined); } await Promise.all([reloadGroupList(), fetchData()]); }; const handleSelectGroup = (item: GroupListItem) => { setSearchGroupId(item.id); setSelectedGroupName(item.id ? item.groupName : undefined); setCurrent(1); }; const hotWordGroupTitle = searchGroupId ? selectedGroupName || groupData.find((item) => item.id === searchGroupId)?.groupName || groupNameMap[searchGroupId] || "热词列表" : "全部热词"; const groupListData: GroupListItem[] = [{ id: undefined, groupName: "全部热词" }, ...groupData]; const columns = [ { title: "热词原文", dataIndex: "word", key: "word", ellipsis: true, render: (text: string) => {text}, }, { title: "拼音", dataIndex: "pinyinList", key: "pinyinList", render: (list: string[]) => list?.[0] ? {list[0]} : -, }, { title: "分类", dataIndex: "category", key: "category", render: (value: string) => categories.find((item) => item.itemValue === value)?.itemLabel || value || "-", }, { title: "热词组", dataIndex: "hotWordGroupId", key: "hotWordGroupId", render: (value?: number, record?: HotWordVO) => { const name = record?.hotWordGroupName || (value ? groupNameMap[value] : undefined); return name ? {name} : 未分组; }, }, { title: "权重", dataIndex: "weight", key: "weight", render: (value: number) => {value}, }, { title: "状态", dataIndex: "status", key: "status", render: (value: number) => value === 1 ? : , }, { title: "操作", key: "action", render: (_: unknown, record: HotWordVO) => ( handleDelete(record.id)} okText={t("common.confirm")} cancelText={t("common.cancel")} > ), }, ]; return (
} onClick={() => openGroupEditor()} size="small" aria-label="新增热词组" /> } >
setGroupSearchInput(e.target.value)} onSearch={(value) => { setGroupSearchInput(value); setGroupSearchName(value.trim()); setGroupCurrent(1); }} /> } allowClear value={searchWord} onChange={(e) => setSearchWord(e.target.value)} onPressEnter={() => { setCurrent(1); void fetchData(); }} className="hotwords-search__word" /> setGroupEditorVisible(false)} onOk={() => void handleGroupSubmit()} confirmLoading={groupSubmitLoading} destroyOnHidden >
); }; export default HotWords;