feat: 添加热词组筛选和分页功能
- 在 `HotWords.tsx` 中添加热词组筛选选项和分页逻辑 - 更新 `hotwordGroup.ts` 和后端相关控制器及服务以支持新的筛选参数 - 优化前端热词组列表的展示和交互逻辑dev_na
parent
0b8014d1af
commit
99f5fd1cbd
|
|
@ -74,10 +74,11 @@ public class HotWordGroupController {
|
|||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) Long tenantId) {
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
Long targetTenantId = resolveTargetTenantId(loginUser, tenantId);
|
||||
return ApiResponse.ok(hotWordGroupService.pageGroups(current, size, name, targetTenantId));
|
||||
return ApiResponse.ok(hotWordGroupService.pageGroups(current, size, name, status, targetTenantId));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询热词组选项")
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public interface HotWordGroupService extends IService<HotWordGroup> {
|
|||
|
||||
boolean removeGroupById(Long id, Long tenantId);
|
||||
|
||||
PageResult<List<HotWordGroupVO>> pageGroups(Integer current, Integer size, String name, Long tenantId);
|
||||
PageResult<List<HotWordGroupVO>> pageGroups(Integer current, Integer size, String name, Integer status, Long tenantId);
|
||||
|
||||
List<HotWordGroupVO> listVisibleOptions(Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,10 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
|
|||
}
|
||||
|
||||
@Override
|
||||
public PageResult<List<HotWordGroupVO>> pageGroups(Integer current, Integer size, String name, Long tenantId) {
|
||||
public PageResult<List<HotWordGroupVO>> pageGroups(Integer current, Integer size, String name, Integer status, Long tenantId) {
|
||||
LambdaQueryWrapper<HotWordGroup> wrapper = new LambdaQueryWrapper<HotWordGroup>()
|
||||
.like(name != null && !name.isBlank(), HotWordGroup::getGroupName, name)
|
||||
.eq(status != null, HotWordGroup::getStatus, status)
|
||||
.orderByDesc(HotWordGroup::getCreatedAt);
|
||||
wrapper.eq(tenantId != null, HotWordGroup::getTenantId, tenantId);
|
||||
Page<HotWordGroup> page = this.page(new Page<>(current, size), wrapper);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const getHotWordGroupPage = (params: {
|
|||
current: number;
|
||||
size: number;
|
||||
name?: string;
|
||||
status?: number;
|
||||
tenantId?: number;
|
||||
}) => {
|
||||
return http.get<{ code: string; data: { records: HotWordGroupVO[]; total: number }; msg: string }>(
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ type HotWordGroupFormValues = {
|
|||
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();
|
||||
|
|
@ -94,7 +96,14 @@ const HotWords: React.FC = () => {
|
|||
const [groupLoading, setGroupLoading] = useState(false);
|
||||
const [groupSubmitLoading, setGroupSubmitLoading] = useState(false);
|
||||
const [groupData, setGroupData] = useState<HotWordGroupVO[]>([]);
|
||||
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<number | undefined>(undefined);
|
||||
const [editingGroupId, setEditingGroupId] = useState<number | null>(null);
|
||||
const [selectedGroupName, setSelectedGroupName] = useState<string | undefined>(undefined);
|
||||
|
||||
const [filterVisible, setFilterVisible] = useState(false);
|
||||
|
||||
|
|
@ -109,8 +118,11 @@ const HotWords: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
void loadGroupOptions();
|
||||
}, [isPlatformAdmin, activeTenantId]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadGroupPage();
|
||||
}, []);
|
||||
}, [groupCurrent, groupSearchName, groupSearchStatus, groupSize, isPlatformAdmin, activeTenantId]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -140,18 +152,29 @@ const HotWords: React.FC = () => {
|
|||
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;
|
||||
}
|
||||
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);
|
||||
|
|
@ -248,7 +271,7 @@ const HotWords: React.FC = () => {
|
|||
message.success("热词组创建成功");
|
||||
}
|
||||
setGroupEditorVisible(false);
|
||||
await Promise.all([loadGroupOptions(), loadGroupPage()]);
|
||||
await reloadGroupList(true);
|
||||
} finally {
|
||||
setGroupSubmitLoading(false);
|
||||
}
|
||||
|
|
@ -260,10 +283,23 @@ const HotWords: React.FC = () => {
|
|||
message.success("热词组删除成功");
|
||||
if (searchGroupId === id) {
|
||||
setSearchGroupId(undefined);
|
||||
setSelectedGroupName(undefined);
|
||||
}
|
||||
await Promise.all([loadGroupOptions(), loadGroupPage(), fetchData()]);
|
||||
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: "热词原文",
|
||||
|
|
@ -359,7 +395,7 @@ const HotWords: React.FC = () => {
|
|||
className="shadow-sm"
|
||||
title="热词组"
|
||||
style={{ width: '25%', display: 'flex', flexDirection: 'column', minWidth: 280 }}
|
||||
styles={{ body: { padding: 0, flex: 1, overflow: 'auto' } }}
|
||||
styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", minHeight: 0 } }}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
|
|
@ -369,69 +405,114 @@ const HotWords: React.FC = () => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<List
|
||||
loading={groupLoading}
|
||||
dataSource={[{ id: undefined, groupName: '全部热词' } as any, ...groupData]}
|
||||
renderItem={(item) => {
|
||||
const isSelected = searchGroupId === item.id;
|
||||
<div style={{ padding: "16px 16px 12px", borderBottom: "1px solid var(--ant-color-border-secondary, #f0f0f0)" }}>
|
||||
<Space direction="vertical" size={12} style={{ width: "100%" }}>
|
||||
<Input.Search
|
||||
placeholder="搜索热词组名称"
|
||||
allowClear
|
||||
value={groupSearchInput}
|
||||
onChange={(e) => setGroupSearchInput(e.target.value)}
|
||||
onSearch={(value) => {
|
||||
setGroupSearchInput(value);
|
||||
setGroupSearchName(value.trim());
|
||||
setGroupCurrent(1);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
placeholder="按状态筛选"
|
||||
allowClear
|
||||
value={groupSearchStatus}
|
||||
style={{ width: "100%" }}
|
||||
options={[
|
||||
{ label: "启用", value: 1 },
|
||||
{ label: "禁用", value: 0 },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setGroupSearchStatus(value);
|
||||
setGroupCurrent(1);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
|
||||
<List
|
||||
loading={groupLoading}
|
||||
dataSource={groupListData}
|
||||
renderItem={(item) => {
|
||||
const isSelected = searchGroupId === item.id;
|
||||
const actions = [];
|
||||
if (item.id) {
|
||||
actions.push(
|
||||
<Button
|
||||
key={`edit-${item.id}`}
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={(e) => openGroupEditor(item, e)}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
actions.push(
|
||||
<Popconfirm
|
||||
key={`delete-${item.id}`}
|
||||
title="确定删除这个热词组吗?"
|
||||
description="删除前必须先解除模板引用并清空组内热词。"
|
||||
onConfirm={(e) => handleDeleteGroup(item.id, e)}
|
||||
onCancel={(e) => e?.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
size="small"
|
||||
/>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
|
||||
const actions = [];
|
||||
if (item.id) {
|
||||
actions.push(
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={(e) => openGroupEditor(item, e)}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
actions.push(
|
||||
<Popconfirm
|
||||
title="确定删除这个热词组吗?"
|
||||
description="删除前必须先解除模板引用并清空组内热词。"
|
||||
onConfirm={(e) => handleDeleteGroup(item.id, e)}
|
||||
onCancel={e => e?.stopPropagation()}
|
||||
return (
|
||||
<List.Item
|
||||
onClick={() => handleSelectGroup(item)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
padding: '12px 24px',
|
||||
background: isSelected ? 'var(--ant-color-primary-bg, #e6f4ff)' : 'transparent',
|
||||
borderBottom: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
|
||||
transition: 'background 0.3s'
|
||||
}}
|
||||
actions={actions}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={e => e.stopPropagation()}
|
||||
size="small"
|
||||
/>
|
||||
</Popconfirm>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<Text strong style={{ color: isSelected ? 'var(--ant-color-primary, #1677ff)' : 'inherit' }}>
|
||||
{item.groupName}
|
||||
</Text>
|
||||
}
|
||||
description={
|
||||
item.id
|
||||
? (
|
||||
<>
|
||||
<Tag color={item.hotWordCount >= 200 ? "red" : item.status === 1 ? "processing" : "default"}>
|
||||
{item.hotWordCount}/200
|
||||
</Tag>
|
||||
{item.remark}
|
||||
</>
|
||||
)
|
||||
: '查看所有热词'
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
onClick={() => {
|
||||
setSearchGroupId(item.id);
|
||||
setCurrent(1);
|
||||
}}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
padding: '12px 24px',
|
||||
background: isSelected ? 'var(--ant-color-primary-bg, #e6f4ff)' : 'transparent',
|
||||
borderBottom: '1px solid var(--ant-color-border-secondary, #f0f0f0)',
|
||||
transition: 'background 0.3s'
|
||||
}}
|
||||
actions={actions}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<Text strong style={{ color: isSelected ? 'var(--ant-color-primary, #1677ff)' : 'inherit' }}>
|
||||
{item.groupName}
|
||||
</Text>
|
||||
}
|
||||
description={
|
||||
item.id
|
||||
? <><Tag color={item.hotWordCount >= 200 ? "red" : "processing"}>{item.hotWordCount}/200</Tag> {item.remark}</>
|
||||
: '查看所有热词'
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<AppPagination
|
||||
current={groupCurrent}
|
||||
pageSize={groupSize}
|
||||
total={groupTotal}
|
||||
onChange={(page, pageSize) => {
|
||||
setGroupCurrent(page);
|
||||
setGroupSize(pageSize);
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
|
@ -439,7 +520,7 @@ const HotWords: React.FC = () => {
|
|||
{/* Right Panel: Hotwords */}
|
||||
<Card
|
||||
className="shadow-sm"
|
||||
title={searchGroupId ? groupData.find(g => g.id === searchGroupId)?.groupName || '热词列表' : '全部热词'}
|
||||
title={hotWordGroupTitle}
|
||||
style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}
|
||||
styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
||||
>
|
||||
|
|
|
|||
Loading…
Reference in New Issue