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