fix: 统一AI详情页面包屑与Tab导航表现

- 为工作流、插件、知识库、聊天助手详情路由恢复面包屑与Tab参与并保持父模块高亮

- 列表卡片跳转统一追加 navTitle 与 pageKey,详情页Tab标题与面包屑优先显示卡片名称

- 工作流设计页、知识库详情页、聊天助手设置页在深链缺少 navTitle 时自动 replace 补写
This commit is contained in:
2026-03-11 20:07:00 +08:00
parent c933971a75
commit 99f792f6de
13 changed files with 146 additions and 13 deletions

View File

@@ -22,8 +22,6 @@ const routes: RouteRecordRaw[] = [
title: 'Bots',
openInNewWindow: true,
hideInMenu: true,
hideInBreadcrumb: true,
hideInTab: true,
activePath: '/ai/bots',
},
},

View File

@@ -7,8 +7,6 @@ const routes: RouteRecordRaw[] = [
meta: {
title: $t('documentCollection.documentManagement'),
hideInMenu: true,
hideInTab: true,
hideInBreadcrumb: true,
fullPathKey: true,
activePath: '/ai/documentCollection',
},

View File

@@ -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',

View File

@@ -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',

View File

@@ -67,6 +67,9 @@ const headerButtons = [
permission: '/api/v1/documentCollection/save',
},
];
function resolveNavTitle(row: BotInfo) {
return (row as Record<string, any>)?.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),
},
});
},
},
{

View File

@@ -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<BotInfo>();
function syncNavTitle(title: string) {
if (!title) {
return;
}
const query = route.query as Record<string, any>;
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);
}
};
</script>

View File

@@ -26,6 +26,27 @@ const activeMenu = ref<string>((route.query.activeMenu as string) || '');
const knowledgeInfo = ref<any>({});
const selectedCategory = ref('');
const syncNavTitle = (title: string) => {
if (!title) {
return;
}
const query = route.query as Record<string, any>;
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,

View File

@@ -33,6 +33,10 @@ const collectionTypeLabelMap = {
DOCUMENT: $t('documentCollection.collectionTypeDocument'),
FAQ: $t('documentCollection.collectionTypeFaq'),
};
function resolveNavTitle(row: Record<string, any>) {
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',
},
});

View File

@@ -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),
},
});
},

View File

@@ -203,6 +203,27 @@ const pluginSelectRef = ref();
const updatePluginNode = ref<any>(null);
const pageLoading = ref(false);
const chainInfo = ref<any>(null);
function syncNavTitle(title: string) {
if (!title) {
return;
}
const query = route.query as Record<string, any>;
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() {

View File

@@ -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),
},
});
}