fix: 统一AI详情页面包屑与Tab导航表现
- 为工作流、插件、知识库、聊天助手详情路由恢复面包屑与Tab参与并保持父模块高亮 - 列表卡片跳转统一追加 navTitle 与 pageKey,详情页Tab标题与面包屑优先显示卡片名称 - 工作流设计页、知识库详情页、聊天助手设置页在深链缺少 navTitle 时自动 replace 补写
This commit is contained in:
@@ -22,8 +22,6 @@ const routes: RouteRecordRaw[] = [
|
||||
title: 'Bots',
|
||||
openInNewWindow: true,
|
||||
hideInMenu: true,
|
||||
hideInBreadcrumb: true,
|
||||
hideInTab: true,
|
||||
activePath: '/ai/bots',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,8 +7,6 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: {
|
||||
title: $t('documentCollection.documentManagement'),
|
||||
hideInMenu: true,
|
||||
hideInTab: true,
|
||||
hideInBreadcrumb: true,
|
||||
fullPathKey: true,
|
||||
activePath: '/ai/documentCollection',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -26,8 +26,34 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
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[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user