From 99f792f6de145c0aad933eb51ef064e403e8d7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Wed, 11 Mar 2026 20:07:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80AI=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8C=85=E5=B1=91=E4=B8=8ETab=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为工作流、插件、知识库、聊天助手详情路由恢复面包屑与Tab参与并保持父模块高亮 - 列表卡片跳转统一追加 navTitle 与 pageKey,详情页Tab标题与面包屑优先显示卡片名称 - 工作流设计页、知识库详情页、聊天助手设置页在深链缺少 navTitle 时自动 replace 补写 --- .../app/src/router/routes/modules/bot.ts | 2 -- .../app/src/router/routes/modules/document.ts | 2 -- .../app/src/router/routes/modules/plugins.ts | 3 +-- .../app/src/router/routes/modules/workflow.ts | 3 +-- .../app/src/views/ai/bots/index.vue | 11 +++++++- .../src/views/ai/bots/pages/setting/index.vue | 25 ++++++++++++++++- .../views/ai/documentCollection/Document.vue | 22 +++++++++++++++ .../documentCollection/DocumentCollection.vue | 6 +++++ .../app/src/views/ai/plugin/Plugin.vue | 5 ++++ .../src/views/ai/workflow/WorkflowDesign.vue | 22 +++++++++++++++ .../src/views/ai/workflow/WorkflowList.vue | 5 ++++ .../layouts/src/basic/tabbar/use-tabbar.ts | 27 ++++++++++++++++--- .../layouts/src/widgets/breadcrumb.vue | 26 ++++++++++++++++++ 13 files changed, 146 insertions(+), 13 deletions(-) diff --git a/easyflow-ui-admin/app/src/router/routes/modules/bot.ts b/easyflow-ui-admin/app/src/router/routes/modules/bot.ts index 6975c89..86c390e 100644 --- a/easyflow-ui-admin/app/src/router/routes/modules/bot.ts +++ b/easyflow-ui-admin/app/src/router/routes/modules/bot.ts @@ -22,8 +22,6 @@ const routes: RouteRecordRaw[] = [ title: 'Bots', openInNewWindow: true, hideInMenu: true, - hideInBreadcrumb: true, - hideInTab: true, activePath: '/ai/bots', }, }, diff --git a/easyflow-ui-admin/app/src/router/routes/modules/document.ts b/easyflow-ui-admin/app/src/router/routes/modules/document.ts index abe9be5..4f6ff33 100644 --- a/easyflow-ui-admin/app/src/router/routes/modules/document.ts +++ b/easyflow-ui-admin/app/src/router/routes/modules/document.ts @@ -7,8 +7,6 @@ const routes: RouteRecordRaw[] = [ meta: { title: $t('documentCollection.documentManagement'), hideInMenu: true, - hideInTab: true, - hideInBreadcrumb: true, fullPathKey: true, activePath: '/ai/documentCollection', }, diff --git a/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts b/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts index 49650ea..e3a59c6 100644 --- a/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts +++ b/easyflow-ui-admin/app/src/router/routes/modules/plugins.ts @@ -7,9 +7,8 @@ const routes: RouteRecordRaw[] = [ meta: { title: $t('plugin.toolsManagement'), hideInMenu: true, - hideInTab: true, - hideInBreadcrumb: true, fullPathKey: true, + activePath: '/ai/plugin', }, name: 'PluginTools', path: '/ai/plugin/tools', diff --git a/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts b/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts index c3d7d82..34df9ec 100644 --- a/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts +++ b/easyflow-ui-admin/app/src/router/routes/modules/workflow.ts @@ -8,8 +8,7 @@ const routes: RouteRecordRaw[] = [ icon: 'ant-design:apartment-outlined', title: $t('datacenterTable.title'), hideInMenu: true, - hideInTab: true, - hideInBreadcrumb: true, + activePath: '/ai/workflow', }, name: 'WorkflowDesign', path: '/ai/workflow/design', diff --git a/easyflow-ui-admin/app/src/views/ai/bots/index.vue b/easyflow-ui-admin/app/src/views/ai/bots/index.vue index 1015205..57f61a0 100644 --- a/easyflow-ui-admin/app/src/views/ai/bots/index.vue +++ b/easyflow-ui-admin/app/src/views/ai/bots/index.vue @@ -67,6 +67,9 @@ const headerButtons = [ permission: '/api/v1/documentCollection/save', }, ]; +function resolveNavTitle(row: BotInfo) { + return (row as Record)?.title || row?.name || ''; +} const actions: ActionButton[] = [ { icon: Edit, @@ -83,7 +86,13 @@ const actions: ActionButton[] = [ className: '', permission: '', onClick(row: BotInfo) { - router.push({ path: `/ai/bots/setting/${row.id}` }); + router.push({ + path: '/ai/bots/setting/' + row.id, + query: { + pageKey: '/ai/bots', + navTitle: resolveNavTitle(row), + }, + }); }, }, { diff --git a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue index 21617e4..e0eac0d 100644 --- a/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue +++ b/easyflow-ui-admin/app/src/views/ai/bots/pages/setting/index.vue @@ -2,7 +2,7 @@ import type {BotInfo} from '@easyflow/types'; import {computed, onMounted, ref} from 'vue'; -import {useRoute} from 'vue-router'; +import {useRoute, useRouter} from 'vue-router'; import {tryit} from 'radash'; @@ -14,11 +14,33 @@ import Preview from './preview.vue'; import Prompt from './prompt.vue'; const route = useRoute(); +const router = useRouter(); const hasSavePermission = computed(() => hasPermission(['/api/v1/bot/save', '/api/v1/bot/updateLlmId']), ); const bot = ref(); +function syncNavTitle(title: string) { + if (!title) { + return; + } + const query = route.query as Record; + const navTitle = Array.isArray(query.navTitle) + ? query.navTitle[0] + : query.navTitle; + if (typeof navTitle === 'string' && navTitle.trim()) { + return; + } + router.replace({ + path: route.path, + query: { + ...query, + pageKey: query.pageKey || '/ai/bots', + navTitle: title, + }, + }); +} + onMounted(() => { if (route.params.id) { fetchBotDetail(route.params.id as string); @@ -30,6 +52,7 @@ const fetchBotDetail = async (id: string) => { if (res?.errorCode === 0) { bot.value = res.data; + syncNavTitle((res.data?.title || res.data?.name || '') as string); } }; diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue index 111dea6..32bd0af 100644 --- a/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/Document.vue @@ -26,6 +26,27 @@ const activeMenu = ref((route.query.activeMenu as string) || ''); const knowledgeInfo = ref({}); const selectedCategory = ref(''); +const syncNavTitle = (title: string) => { + if (!title) { + return; + } + const query = route.query as Record; + const navTitle = Array.isArray(query.navTitle) + ? query.navTitle[0] + : query.navTitle; + if (typeof navTitle === 'string' && navTitle.trim()) { + return; + } + router.replace({ + path: route.path, + query: { + ...query, + pageKey: query.pageKey || '/ai/documentCollection', + navTitle: title, + }, + }); +}; + const resolveDefaultMenu = (collectionType: string, menuKey: string) => { const faqMenus = new Set(['config', 'faqList', 'knowledgeSearch']); const documentMenus = new Set(['config', 'documentList', 'knowledgeSearch']); @@ -46,6 +67,7 @@ const getKnowledge = () => { .then((res) => { if (res.errorCode === 0) { knowledgeInfo.value = res.data; + syncNavTitle(res.data?.title || ''); const initialMenu = resolveDefaultMenu( res.data.collectionType || 'DOCUMENT', activeMenu.value, diff --git a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue index 0ffb7a3..d868773 100644 --- a/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue +++ b/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollection.vue @@ -33,6 +33,10 @@ const collectionTypeLabelMap = { DOCUMENT: $t('documentCollection.collectionTypeDocument'), FAQ: $t('documentCollection.collectionTypeFaq'), }; + +function resolveNavTitle(row: Record) { + return row?.title || row?.name || ''; +} interface FieldDefinition { // 字段名称 prop: string; @@ -67,6 +71,7 @@ const actions: ActionButton[] = [ query: { id: row.id, pageKey: '/ai/documentCollection', + navTitle: resolveNavTitle(row), }, }); }, @@ -82,6 +87,7 @@ const actions: ActionButton[] = [ query: { id: row.id, pageKey: '/ai/documentCollection', + navTitle: resolveNavTitle(row), activeMenu: 'knowledgeSearch', }, }); diff --git a/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue index 499b588..73795f2 100644 --- a/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue +++ b/easyflow-ui-admin/app/src/views/ai/plugin/Plugin.vue @@ -48,6 +48,10 @@ interface CategoryFormData { name: string; } +function resolveNavTitle(item: PluginRecord) { + return (item.title as string) || (item.name as string) || ''; +} + // 操作按钮配置 const actions: ActionButton[] = [ { @@ -70,6 +74,7 @@ const actions: ActionButton[] = [ query: { id: item.id, pageKey: '/ai/plugin', + navTitle: resolveNavTitle(item), }, }); }, diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue index 2ec9a53..6c38ba1 100644 --- a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue @@ -203,6 +203,27 @@ const pluginSelectRef = ref(); const updatePluginNode = ref(null); const pageLoading = ref(false); const chainInfo = ref(null); + +function syncNavTitle(title: string) { + if (!title) { + return; + } + const query = route.query as Record; + const navTitle = Array.isArray(query.navTitle) + ? query.navTitle[0] + : query.navTitle; + if (typeof navTitle === 'string' && navTitle.trim()) { + return; + } + router.replace({ + path: route.path, + query: { + ...query, + pageKey: query.pageKey || '/ai/workflow', + navTitle: title, + }, + }); +} // functions async function loadCustomNode() { customNode.value = await getCustomNode({ @@ -264,6 +285,7 @@ async function getWorkflowInfo(workflowId: any) { tinyFlowData.value = workflowInfo.value.content ? JSON.parse(workflowInfo.value.content) : {}; + syncNavTitle(workflowInfo.value?.title || ''); }); } async function getLlmList() { diff --git a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue index 97012de..439ff0f 100644 --- a/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue +++ b/easyflow-ui-admin/app/src/views/ai/workflow/WorkflowList.vue @@ -170,6 +170,9 @@ function reset() { function showDialog(row: any, importMode = false) { saveDialog.value.openDialog({ ...row }, importMode); } +function resolveNavTitle(row: any) { + return row?.title || row?.name || ''; +} function remove(row: any) { ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), { confirmButtonText: $t('message.ok'), @@ -202,6 +205,8 @@ function toDesignPage(row: any) { name: 'WorkflowDesign', query: { id: row.id, + pageKey: '/ai/workflow', + navTitle: resolveNavTitle(row), }, }); } diff --git a/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts b/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts index 8060c04..8fa6e41 100644 --- a/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts +++ b/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts @@ -85,12 +85,27 @@ export function useTabbar() { await closeTabByKey(key); }; + function resolveNavTitle(navTitleQuery: unknown): string { + const rawValue = Array.isArray(navTitleQuery) + ? navTitleQuery[0] + : navTitleQuery; + if (typeof rawValue !== 'string' || !rawValue.trim()) { + return ''; + } + try { + return decodeURIComponent(rawValue); + } catch { + return rawValue; + } + } + function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) { + const navTitle = tab?.meta?.navTitle as string | undefined; return { ...tab, meta: { ...tab?.meta, - title: $t(tab?.meta?.title as string), + title: navTitle || $t(tab?.meta?.title as string), }, }; } @@ -106,10 +121,16 @@ export function useTabbar() { watch( () => route.fullPath, () => { - const meta = route.matched?.[route.matched.length - 1]?.meta; + const routeMeta = route.matched?.[route.matched.length - 1]?.meta; + const navTitle = resolveNavTitle(route.query.navTitle); + const meta = { + ...(routeMeta || route.meta), + navTitle, + title: navTitle || (routeMeta?.title ?? route.meta?.title), + }; tabbarStore.addTab({ ...route, - meta: meta || route.meta, + meta, }); }, { immediate: true }, diff --git a/easyflow-ui-admin/packages/effects/layouts/src/widgets/breadcrumb.vue b/easyflow-ui-admin/packages/effects/layouts/src/widgets/breadcrumb.vue index c09e797..d4a5cc6 100644 --- a/easyflow-ui-admin/packages/effects/layouts/src/widgets/breadcrumb.vue +++ b/easyflow-ui-admin/packages/effects/layouts/src/widgets/breadcrumb.vue @@ -26,8 +26,34 @@ const props = withDefaults(defineProps(), { const route = useRoute(); const router = useRouter(); +function resolveNavTitle(navTitleQuery: unknown): string { + const rawValue = Array.isArray(navTitleQuery) + ? navTitleQuery[0] + : navTitleQuery; + if (typeof rawValue !== 'string' || !rawValue.trim()) { + return ''; + } + try { + return decodeURIComponent(rawValue); + } catch { + return rawValue; + } +} + const breadcrumbs = computed((): IBreadcrumb[] => { const matched = route.matched; + const navTitle = resolveNavTitle(route.query.navTitle); + + if (navTitle) { + const currentRoute = matched[matched.length - 1]; + return [ + { + icon: currentRoute?.meta?.icon, + path: currentRoute?.path || route.path, + title: navTitle, + }, + ]; + } const resultBreadcrumb: IBreadcrumb[] = [];