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 { DeleteOutlined, EditOutlined, PlusOutlined, } 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 ListActionBar from "../../components/shared/ListActionBar/ListActionBar"; 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; }; 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 [editingGroupId, setEditingGroupId] = useState(null); const [filterVisible, setFilterVisible] = useState(false); 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(); void loadGroupPage(); }, []); 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); } } 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: 1, size: 200 }); if (isPlatformAdmin) { const scoped = await getHotWordGroupPage({ current: 1, size: 200, tenantId: activeTenantId }); setGroupData(scoped.data?.data?.records || []); return; } setGroupData(res.data?.data?.records || []); } finally { setGroupLoading(false); } }; 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 Promise.all([loadGroupOptions(), loadGroupPage()]); } 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); } await Promise.all([loadGroupOptions(), loadGroupPage(), fetchData()]); }; 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")} > ), }, ]; const filterContent = (
分类筛选
); return (
{/* Left Panel: Hotword Groups */} } onClick={() => openGroupEditor()} size="small" /> } > { const isSelected = searchGroupId === item.id; const actions = []; if (item.id) { actions.push(