feat: 统一列表模糊搜索行为

- 统一管理端和用户中心搜索参数及多字段包含匹配

- 修复聊天搜索竞态并优化部门重名路径展示

- 补充部门展开、模型空白词和聊天查询回归测试
This commit is contained in:
2026-08-13 22:29:59 +08:00
parent 765006747a
commit 64a85c6a5b
83 changed files with 1134 additions and 208 deletions

View File

@@ -79,9 +79,7 @@ watchListRouteState(route, agentListRouteSchema, (state) => {
pageSize: state.pageSize,
queryParams: {
categoryId: state.categoryId || undefined,
description: state.keyword || undefined,
isQueryOr: true,
name: state.keyword || undefined,
keyword: state.keyword || undefined,
},
});
});
@@ -187,9 +185,7 @@ function handleSearch(keyword: string) {
searchKeyword.value = keyword;
pageDataRef.value?.setQuery({
categoryId: selectedCategoryId.value || undefined,
isQueryOr: true,
name: keyword || undefined,
description: keyword || undefined,
keyword: keyword || undefined,
});
}
@@ -268,9 +264,7 @@ function changeCategory(category: any) {
selectedCategoryId.value = categoryId;
pageDataRef.value?.setQuery({
categoryId: categoryId || undefined,
isQueryOr: true,
name: searchKeyword.value || undefined,
description: searchKeyword.value || undefined,
keyword: searchKeyword.value || undefined,
});
}
@@ -290,9 +284,7 @@ async function loadCategories() {
selectedCategoryId.value = '';
initialCategoryChangePending = false;
pageDataRef.value?.setQuery?.({
isQueryOr: true,
name: searchKeyword.value || undefined,
description: searchKeyword.value || undefined,
keyword: searchKeyword.value || undefined,
});
}
}
@@ -452,9 +444,7 @@ async function handleDeleteAction(row: AgentInfo) {
:initial-page-number="initialListState.pageNumber"
:initial-query-params="{
categoryId: initialListState.categoryId || undefined,
isQueryOr: true,
name: initialListState.keyword || undefined,
description: initialListState.keyword || undefined,
keyword: initialListState.keyword || undefined,
}"
@state-change="handlePageStateChange"
>

View File

@@ -100,8 +100,7 @@ watchListRouteState(route, botListRouteSchema, (state) => {
pageSize: state.pageSize,
queryParams: {
categoryId: state.categoryId || undefined,
isQueryOr: true,
title: state.keyword || undefined,
keyword: state.keyword || undefined,
},
});
});
@@ -310,8 +309,7 @@ const handleSearch = (params: string) => {
searchKeyword.value = params;
pageDataRef.value.setQuery({
categoryId: selectedCategoryId.value || undefined,
title: params || undefined,
isQueryOr: true,
keyword: params || undefined,
});
};
const handleButtonClick = () => {
@@ -395,8 +393,7 @@ function changeCategory(category: any) {
selectedCategoryId.value = categoryId;
pageDataRef.value.setQuery({
categoryId: categoryId || undefined,
title: searchKeyword.value || undefined,
isQueryOr: true,
keyword: searchKeyword.value || undefined,
});
}
function showControlDialog(item: any) {
@@ -471,8 +468,7 @@ const getSideList = async () => {
selectedCategoryId.value = '';
initialCategoryChangePending = false;
pageDataRef.value?.setQuery?.({
title: searchKeyword.value || undefined,
isQueryOr: true,
keyword: searchKeyword.value || undefined,
});
}
}
@@ -538,8 +534,7 @@ function handlePageStateChange(state: {
:initial-page-number="initialListState.pageNumber"
:initial-query-params="{
categoryId: initialListState.categoryId || undefined,
title: initialListState.keyword || undefined,
isQueryOr: true,
keyword: initialListState.keyword || undefined,
}"
@state-change="handlePageStateChange"
>

View File

@@ -101,6 +101,7 @@ const loadingAssistants = ref(false);
const loadingKnowledgeOptions = ref(false);
const sending = ref(false);
const sessionSearchKeyword = ref('');
const appliedSessionSearchKeyword = ref('');
const currentAssistantId = ref<string>();
const currentAssistantDetail = ref<any>();
const currentSession = ref<any>();
@@ -122,6 +123,7 @@ const sessionContextMenuVisible = ref(false);
const sessionContextMenuTarget = ref<null | SessionItem>(null);
const sessionContextMenuX = ref(0);
const sessionContextMenuY = ref(0);
let latestSessionRequestId = 0;
const variantSwitchController = createChatVariantSwitchController<
ChatTimeHistoryRecord,
@@ -242,17 +244,7 @@ const extraKnowledgeOptionList = computed(() => {
return knowledgeOptions.value.filter((item) => !excluded.has(String(item.value)));
});
const filteredSessions = computed(() => {
const keyword = sessionSearchKeyword.value.trim().toLowerCase();
if (!keyword) {
return sessions.value;
}
return sessions.value.filter((session) => {
return [
resolveSessionLabel(session),
session.lastMessagePreview,
session.assistantName,
].some((value) => String(value || '').toLowerCase().includes(keyword));
});
return sessions.value;
});
const groupedSessions = computed(() => {
const today: SessionItem[] = [];
@@ -432,14 +424,21 @@ async function fetchSessions(options: { append?: boolean } = {}) {
loadingMoreSessions.value = true;
} else {
loadingSessions.value = true;
loadingMoreSessions.value = false;
}
const requestId = ++latestSessionRequestId;
const nextPageNumber = append ? sessionPage.value.pageNumber + 1 : 1;
const requestKeyword = appliedSessionSearchKeyword.value;
const [, res] = await tryit(api.get)('/api/v1/chatWorkspace/sessions', {
params: {
keyword: requestKeyword || undefined,
pageNumber: nextPageNumber,
pageSize: sessionPage.value.pageSize,
},
});
if (requestId !== latestSessionRequestId) {
return;
}
if (append) {
loadingMoreSessions.value = false;
} else {
@@ -458,6 +457,13 @@ async function fetchSessions(options: { append?: boolean } = {}) {
}
}
function handleSessionSearch() {
const keyword = sessionSearchKeyword.value.trim();
sessionSearchKeyword.value = keyword;
appliedSessionSearchKeyword.value = keyword;
void fetchSessions();
}
function mergeFreshSessionRecords(records: SessionItem[]) {
const freshIds = new Set(records.map((item) => String(item.sessionId)));
const optimisticIds = new Set(optimisticSessionIds.value);
@@ -1581,6 +1587,8 @@ async function deleteSession(targetSession?: SessionItem) {
clearable
:prefix-icon="Search"
placeholder="搜索会话"
@clear="handleSessionSearch"
@keyup.enter="handleSessionSearch"
/>
</div>
@@ -1661,7 +1669,7 @@ async function deleteSession(targetSession?: SessionItem) {
class="chat-workspace__empty"
/>
<ElEmpty
v-else-if="sessionSearchKeyword"
v-else-if="appliedSessionSearchKeyword"
description="未找到匹配会话"
class="chat-workspace__empty"
/>

View File

@@ -405,9 +405,11 @@ function closeDetail() {
<ElInput
v-if="isSuperAdmin"
v-model="query.userAccount"
clearable
class="chat-history-page__filter-control is-input"
placeholder="搜索聊天用户"
placeholder="搜索用户账号"
:prefix-icon="Search"
@clear="handleSearch"
@keyup.enter="handleSearch"
/>
</div>

View File

@@ -91,8 +91,7 @@ watchListRouteState(route, documentCollectionListRouteSchema, (state) => {
pageSize: state.pageSize,
queryParams: {
categoryId: state.categoryId || undefined,
isQueryOr: true,
title: state.keyword || undefined,
keyword: state.keyword || undefined,
},
});
});
@@ -590,8 +589,7 @@ const handleSearch = (params: any) => {
searchKeyword.value = String(params || '');
pageDataRef.value.setQuery({
categoryId: selectedCategoryId.value || undefined,
title: searchKeyword.value || undefined,
isQueryOr: true,
keyword: searchKeyword.value || undefined,
});
};
const reloadKnowledgeList = () => {
@@ -631,8 +629,7 @@ const getCategoryList = async () => {
selectedCategoryId.value = '';
initialCategoryChangePending = false;
pageDataRef.value?.setQuery?.({
title: searchKeyword.value || undefined,
isQueryOr: true,
keyword: searchKeyword.value || undefined,
});
}
}
@@ -763,8 +760,7 @@ function changeCategory(category: any) {
selectedCategoryId.value = categoryId;
pageDataRef.value.setQuery({
categoryId: categoryId || undefined,
title: searchKeyword.value || undefined,
isQueryOr: true,
keyword: searchKeyword.value || undefined,
});
}
function handlePageStateChange(state: {
@@ -822,8 +818,7 @@ function handlePageStateChange(state: {
:initial-page-number="initialListState.pageNumber"
:initial-query-params="{
categoryId: initialListState.categoryId || undefined,
title: initialListState.keyword || undefined,
isQueryOr: true,
keyword: initialListState.keyword || undefined,
}"
@state-change="handlePageStateChange"
>

View File

@@ -100,7 +100,7 @@ defineExpose({
},
search(searchText: string) {
pageDataRef.value?.setQuery?.({
title: searchText,
keyword: searchText.trim() || undefined,
});
},
});

View File

@@ -91,7 +91,7 @@ const treeData = computed(() => [
const refreshList = () => {
const query: Record<string, any> = {};
if (searchKeyword.value.trim()) {
query.question = searchKeyword.value.trim();
query.keyword = searchKeyword.value.trim();
}
if (selectedCategoryId.value !== 'all') {
query.categoryId = selectedCategoryId.value;

View File

@@ -117,7 +117,7 @@ const headerButtons = [
},
];
const handleSearch = (params: string) => {
pageDataRef.value.setQuery({ packageName: params, isQueryOr: true });
pageDataRef.value.setQuery({ keyword: params || undefined });
};
const handleHeaderButtonClick = (button: any) => {
if (button.key === 'create') {

View File

@@ -6,26 +6,23 @@ import {
} from './plugin-query';
describe('plugin query params', () => {
it('uses the persisted name field and an explicit like operator', () => {
it('uses the unified keyword for plugin name and description search', () => {
expect(buildPluginPageQueryParams('7', ' 1213 ')).toEqual({
category: '7',
name: '1213',
name__op: 'like',
keyword: '1213',
});
});
it('retains category while clearing the keyword', () => {
expect(buildPluginPageQueryParams('7', ' ')).toEqual({
category: '7',
name: undefined,
name__op: undefined,
keyword: undefined,
});
});
it('uses contains matching for plugin tool names', () => {
it('uses the unified keyword for plugin tool name and description search', () => {
expect(buildPluginToolPageQueryParams(' HTTP ')).toEqual({
name: 'HTTP',
name__op: 'like',
keyword: 'HTTP',
});
});
});

View File

@@ -3,19 +3,17 @@ function normalizeKeyword(keyword?: string): string {
}
function buildPluginPageQueryParams(categoryId: string, keyword?: string) {
const name = normalizeKeyword(keyword);
const normalizedKeyword = normalizeKeyword(keyword);
return {
category: categoryId || '0',
name: name || undefined,
name__op: name ? 'like' : undefined,
keyword: normalizedKeyword || undefined,
};
}
function buildPluginToolPageQueryParams(keyword?: string) {
const name = normalizeKeyword(keyword);
const normalizedKeyword = normalizeKeyword(keyword);
return {
name: name || undefined,
name__op: name ? 'like' : undefined,
keyword: normalizedKeyword || undefined,
};
}

View File

@@ -56,6 +56,7 @@ const formRef = ref<FormInstance>();
const pageDataRef = ref();
const saveDialog = ref();
const previewDialog = ref();
const selectedCategoryId = ref('');
const formInline = ref({
resourceName: '',
resourceType: '',
@@ -68,13 +69,19 @@ function initDict() {
function search(formEl: FormInstance | undefined) {
formEl?.validate((valid) => {
if (valid) {
pageDataRef.value.setQuery(formInline.value);
pageDataRef.value.setQuery({
categoryId: selectedCategoryId.value || undefined,
keyword: formInline.value.resourceName.trim() || undefined,
resourceType: formInline.value.resourceType || undefined,
});
}
});
}
function reset(formEl: FormInstance | undefined) {
formEl?.resetFields();
pageDataRef.value.setQuery({});
pageDataRef.value.setQuery({
categoryId: selectedCategoryId.value || undefined,
});
}
function showDialog(row: any) {
saveDialog.value.openDialog({ ...row });
@@ -177,7 +184,12 @@ const sideFormRules = computed(() => {
const sideSaveLoading = ref(false);
function changeCategory(category: any) {
pageDataRef.value.setQuery({ categoryId: category.id });
selectedCategoryId.value = String(category.id || '');
pageDataRef.value.setQuery({
categoryId: selectedCategoryId.value || undefined,
keyword: formInline.value.resourceName.trim() || undefined,
resourceType: formInline.value.resourceType || undefined,
});
}
function showControlDialog(item: any) {
sideFormRef.value?.resetFields();

View File

@@ -310,7 +310,7 @@ const currentPageSize = ref(initialListState.pageSize);
const suppressInitialCategoryChange = ref(Boolean(initialListState.categoryId));
const initialQueryParams = {
categoryId: initialListState.categoryId || undefined,
title: initialListState.keyword || undefined,
keyword: initialListState.keyword || undefined,
};
watchListRouteState(route, workflowListRouteSchema, (state) => {
if (String(state.categoryId) !== String(selectedCategoryId.value)) {
@@ -325,7 +325,7 @@ watchListRouteState(route, workflowListRouteSchema, (state) => {
pageSize: state.pageSize,
queryParams: {
categoryId: state.categoryId || undefined,
title: state.keyword || undefined,
keyword: state.keyword || undefined,
},
});
});
@@ -401,7 +401,7 @@ async function updateVisibilityScope(
function applyFilters() {
pageDataRef.value?.setQuery({
categoryId: selectedCategoryId.value || undefined,
title: searchKeyword.value || undefined,
keyword: searchKeyword.value || undefined,
});
}
function currentListState(): WorkflowListRouteState {

View File

@@ -66,7 +66,7 @@ watchListRouteState($route, workflowExecListRouteSchema, (state) => {
const restored = pageDataRef.value?.restoreState?.({
pageNumber: state.pageNumber,
pageSize: state.pageSize,
queryParams: { execKey: state.execKey || undefined },
queryParams: { keyword: state.execKey || undefined },
});
if (workflowChanged && !restored) pageDataRef.value?.reload?.();
});
@@ -79,7 +79,7 @@ function search(formEl: FormInstance | undefined) {
if (valid) {
appliedExecKey.value = formInline.value.execKey;
pageDataRef.value.setQuery({
execKey: appliedExecKey.value || undefined,
keyword: appliedExecKey.value || undefined,
});
}
});
@@ -222,7 +222,7 @@ function getTagType(row: any) {
:page-size="initialListState.pageSize"
:initial-page-number="initialListState.pageNumber"
:initial-query-params="{
execKey: initialListState.execKey || undefined,
keyword: initialListState.execKey || undefined,
}"
:extra-query-params="{
workflowId: $route.query.workflowId,

View File

@@ -37,7 +37,9 @@ function initDict() {
function search(formEl: FormInstance | undefined) {
formEl?.validate((valid) => {
if (valid) {
pageDataRef.value.setQuery(formInline.value);
pageDataRef.value.setQuery({
keyword: formInline.value.nodeName.trim() || undefined,
});
}
});
}