Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 18 additions & 51 deletions ui/src/layout/sidebar/ResourceDetailMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,52 @@ const props = defineProps<{ menuItems: LayoutMenuItem[]; activeName: string }>()
const emit = defineEmits<{ select: [menuItem: LayoutMenuItem] }>()

/* 初始展开分组;导航时展开当前页所属分组,保留用户对其他分组的选择。 */
const expandedGroups = ref<string[]>(props.menuItems.filter((menuItem) => menuItem.children?.length).map((menuItem) => menuItem.name))
const expandedGroups = ref<Record<string, boolean>>(
Object.fromEntries(props.menuItems.filter((menuItem) => menuItem.children?.length).map((menuItem) => [menuItem.name, true])),
)

watch(
() => props.activeName,
(activeName) => {
const activeGroup = props.menuItems.find((menuItem) => menuItem.children?.some((child) => child.name === activeName))
if (activeGroup && !expandedGroups.value.includes(activeGroup.name)) expandedGroups.value.push(activeGroup.name)
if (activeGroup) expandedGroups.value[activeGroup.name] = true
},
{ immediate: true },
)
</script>

<template>
<el-collapse v-model="expandedGroups" class="resource-detail-menu space-y-1 border-0! px-4">
<div class="space-y-1 px-4">
<template v-for="menuItem in props.menuItems" :key="menuItem.name">
<!-- 展开或收起菜单分组 -->
<el-collapse-item v-if="menuItem.children?.length" :name="menuItem.name">
<template #title>
<span class="flex-align-center min-w-0 gap-3">
<MkIcon v-if="menuItem.icon" :name="menuItem.icon" />
<MkCollapse
v-if="menuItem.children?.length"
v-model:expanded="expandedGroups[menuItem.name]"
indicator-position="after"
trigger-class="rounded-md px-2 py-[9px] text-N900 hover:bg-N900/10"
>
<template #label>
<span class="flex-align-center min-w-0 flex-1 gap-2">
<MkIcon v-if="menuItem.icon" :name="menuItem.icon" :size="18" />
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
</span>
</template>
<template #icon="{ isActive }">
<MkIcon name="icon_down_outlined" class="ml-2 text-N500 transition-transform" :class="{ 'rotate-180': isActive }" />
</template>
<div class="space-y-1 pt-1">
<template v-for="child in menuItem.children" :key="child.name">
<!-- 切换二级菜单页面 -->
<MkListItem :active="child.name === props.activeName" @click="emit('select', child)">
<span class="ml-7 min-w-0 flex-1 truncate" :title="child.label">{{ child.label }}</span>
<span class="ml-6 min-w-0 flex-1 truncate" :title="child.label">{{ child.label }}</span>
</MkListItem>
</template>
</div>
</el-collapse-item>
</MkCollapse>
<!-- 切换一级菜单页面 -->
<MkListItem v-else :active="menuItem.name === props.activeName" @click="emit('select', menuItem)">
<template #default="{ active }">
<MkIcon v-if="menuItem.icon" class="mr-3" :name="active ? (menuItem.activeIcon ?? menuItem.icon) : menuItem.icon" />
<MkIcon v-if="menuItem.icon" class="mr-2" :name="active ? (menuItem.activeIcon ?? menuItem.icon) : menuItem.icon" :size="18" />
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
</template>
</MkListItem>
</template>
</el-collapse>
</div>
</template>

<style lang="scss" scoped>
/* 详情导航折叠分组与 MkListItem 使用相同的行尺寸和悬停样式。 */
.resource-detail-menu {
:deep(.el-collapse-item__content) {
color: var(--mk-N900);
font-size: var(--mk-font-size-base);
line-height: var(--mk-line-height-base);
padding-bottom: 0;
}

:deep(.el-collapse-item__header) {
background: transparent;
border: 0;
border-radius: var(--el-border-radius-base);
color: var(--mk-N900);
font-size: var(--mk-font-size-base);
font-weight: 400;
height: auto;
line-height: var(--mk-line-height-base);
padding: 9px calc(var(--spacing) * 2);

&:hover {
background: rgb(var(--mk-N900-rgb) / 10%);
}
}

:deep(.el-collapse-item__title) {
min-width: 0;
}

:deep(.el-collapse-item__wrap) {
background: transparent;
border: 0;
}
}
</style>
11 changes: 11 additions & 0 deletions ui/src/router/ROUTE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,14 @@ Workspace 父路由为 `workspace-application-detail-layout`;System 详情实
新增或调整详情页时在路由中维护这些条件,不在工作流返回函数中新增页面名称分支。
无可访问子页面或详情父路由尚未实现时,分别返回 `workspace-application-list` 或
`system-resource-applications`。

### 智能体详情导航

详情导航依次显示概览、设置、接入第三方、对话用户、操作日志。
`detailMenuVisible(params)` 仅用于详情菜单的业务类型筛选,不用于权限判断,也不影响一级导航。
简易设置保留 `setting` 和 `workspace-application-simple-setting`;高级设置使用
`workflow-entry` 和 `workspace-application-workflow-setting`,重定向至 `workflow-application`,
携带当前工作空间、智能体 ID 和 query。两个入口都显示“设置”,按 `APPLICATION_TYPE` 互斥显示。
高级设置的 `canAccess` 固定返回 false,避免工作流返回时重新进入画布;原有概览和简易设置的
返回权限条件保持不变。新增页面未添加权限判断。
`integration`、`chat-user`、`operation-log` 分别对应接入第三方、对话用户、操作日志占位页面。
2 changes: 2 additions & 0 deletions ui/src/router/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ declare module 'vue-router' {
detailActiveMenu?: string
/** 资源详情导航根,仅配置在挂载详情容器的路由上。 */
resourceDetailRoot?: boolean
/** 按资源类型等业务参数决定详情菜单是否显示,不用于权限判断。 */
detailMenuVisible?: (params: RouteParamsGeneric) => boolean
/** iconfont Symbol ID */
icon?: string
order?: number
Expand Down
9 changes: 5 additions & 4 deletions ui/src/router/admin/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RouteLocationNormalizedLoaded, RouteMeta, RouteRecordName, RouteRecordRaw } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteParamsGeneric, RouteMeta, RouteRecordName, RouteRecordRaw } from 'vue-router'
import type { LayoutMenuItem } from '@/layout/types'
import type { RouteScope } from './types'
import { systemRoutes } from './system'
Expand All @@ -13,12 +13,13 @@ interface MenuRouteRecord {
const scopedRoutes: RouteRecordRaw[] = [workspaceRoutes, systemRoutes]

/** 将路由记录递归转换为 Layout 菜单。 */
function createMenuItems(routes: readonly MenuRouteRecord[]): LayoutMenuItem[] {
function createMenuItems(routes: readonly MenuRouteRecord[], detailParams?: RouteParamsGeneric): LayoutMenuItem[] {
return routes
.filter((route) => route.name && route.meta?.title && route.meta.hidden !== true)
.filter((route) => !detailParams || (route.meta?.detailMenuVisible?.(detailParams) ?? true))
.sort((a, b) => (a.meta?.order ?? Number.MAX_SAFE_INTEGER) - (b.meta?.order ?? Number.MAX_SAFE_INTEGER))
.map((route) => {
const children = route.children ? createMenuItems(route.children) : []
const children = route.children ? createMenuItems(route.children, detailParams) : []

return {
name: String(route.name),
Expand All @@ -43,5 +44,5 @@ export function getChildRouteList(scope: RouteScope): LayoutMenuItem[] {
export function getMatchedChildRouteList(route: RouteLocationNormalizedLoaded): LayoutMenuItem[] {
const detailRoot = route.matched.find((matchedRoute) => matchedRoute.meta.resourceDetailRoot === true)

return createMenuItems(detailRoot?.children ?? [])
return createMenuItems(detailRoot?.children ?? [], route.params)
}
37 changes: 37 additions & 0 deletions ui/src/router/admin/workspace/modules/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,46 @@ export const applicationRoutes: RouteRecordRaw[] = [
icon: 'icon_setting',
activeIcon: 'icon_setting_filled',
order: 20,
detailMenuVisible: (params) => params.type === APPLICATION_TYPE.SIMPLE,
canAccess: (params) => params.type === APPLICATION_TYPE.SIMPLE && perm.application.workspace.edit(String(params.applicationId)),
},
},
{
path: 'workflow-entry',
name: 'workspace-application-workflow-setting',
redirect: (to) => ({
name: 'workflow-application',
params: { workspaceId: to.params.workspaceId, applicationId: to.params.applicationId },
query: to.query,
}),
meta: {
title: '设置',
icon: 'icon_setting',
activeIcon: 'icon_setting_filled',
order: 20,
detailMenuVisible: (params) => params.type === APPLICATION_TYPE.WORK_FLOW,
// 工作流跳转入口不能作为画布返回的落脚页。
canAccess: () => false,
},
},
{
path: 'integration',
name: 'workspace-application-integration',
component: () => import('@/views/application-detail/integration/IntegrationView.vue'),
meta: { title: '接入第三方', icon: 'icon_dataset_outlined', activeIcon: 'icon_dataset_filled', order: 30 },
},
{
path: 'chat-user',
name: 'workspace-application-chat-user',
component: () => import('@/views/application-detail/chat-user/ChatUserListView.vue'),
meta: { title: '对话用户', icon: 'icon_contacts_outlined', activeIcon: 'icon_contacts_outlined', order: 40 },
},
{
path: 'operation-log',
name: 'workspace-application-operation-log',
component: () => import('@/views/application-detail/operation-log/OperationLogView.vue'),
meta: { title: '操作日志', icon: 'icon_logs_outlined', activeIcon: 'icon_logs_filled', order: 50 },
},
],
},
],
Expand Down
6 changes: 3 additions & 3 deletions ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,8 @@ npm run build-only-chat
## 资源详情导航

`layout/sidebar/ResourceDetailMenu.vue` 统一资源详情的单层与二级菜单样式,供
`ResourceDetailLayout` 使用。分组使用 `el-collapse / el-collapse-item`,允许同时展开多个分组;
`ResourceDetailLayout` 使用。分组使用 `MkCollapse`,允许同时展开多个分组;
叶子菜单复用 `MkListItem`,二级项仅缩进内容,选中背景保持整行宽度。
折叠标题沿用菜单行的字号、行高、圆角、内边距与悬停颜色,移除 Collapse 默认分隔线和内容留白。
覆盖样式限定在组件 scoped 范围内,不影响 SystemSidebar 或其他 Collapse。
通过 `indicator-position="after"` 将箭头放到右侧,通过 `trigger-class` 统一标题的圆角、
内边距与悬停颜色,标题使用 `label` 插槽;无需额外的深层样式覆盖。
分组首次默认展开,路由切换时展开当前叶子所属分组,普通渲染不重置手动收起状态。
7 changes: 7 additions & 0 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -780,3 +780,10 @@ application、tool、knowledge 的 WorkflowView 统一使用一个 `loading` 控
关闭动画结束后卸载。列表复用 `MkComplexSearch`、`MkTable` 和 `MkStatusLabel`,支持发起人、
状态筛选及跨页浏览,每 6 秒刷新;关闭或卸载停止轮询。待执行、执行中任务经确认后取消,成功刷新列表。
抽屉接收完整知识库 Workflow API;详情查询复用调试任务接口,使用 `ExecutionDetailContent` 展示节点结果。

## 智能体详情占位页面

`application-detail/integration/IntegrationView.vue`、`chat-user/ChatUserListView.vue`、
`operation-log/OperationLogView.vue` 分别承接接入第三方、对话用户、操作日志,目前仅展示占位内容,
标题及详情框架继续由 `ResourceDetailLayout` 提供。设置菜单按资源类型选择现有简易设置页面
或高级智能体全屏工作流,不另建高级设置 View。
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
defineOptions({ name: 'ChatUserListView' })
</script>

<template>
<div class="flex-center min-h-0 flex-1 text-N500">对话用户功能待接入</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
defineOptions({ name: 'IntegrationView' })
</script>

<template>
<div class="flex-center min-h-0 flex-1 text-N500">接入第三方功能待接入</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script setup lang="ts">
defineOptions({ name: 'OperationLogView' })
</script>

<template>
<div class="flex-center min-h-0 flex-1 text-N500">操作日志功能待接入</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ watch(
<template>
<ResourceDetailLayout :loading="loading" @back="handleBack">
<template #resource-header>
<KnowledgeIcon v-if="knowledge" :type="knowledge.type" />
<KnowledgeIcon :type="knowledge?.type" />
<h6 class="min-w-0 truncate" :title="knowledge?.name">{{ knowledge?.name }}</h6>
</template>
</ResourceDetailLayout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { Share } from '@element-plus/icons-vue'
import type KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge'
import type { KnowledgeItem } from '@/api/types'
import { MsgSuccess } from '@/utils/message'
Expand Down Expand Up @@ -27,7 +26,7 @@ function handleKeywordIndex() {
<template>
<!-- 执行分词索引 -->
<MkDropdownItem :disabled="loading" @click.stop="handleKeywordIndex">
<template #icon><MkIcon :icon="Share" /></template>
<template #icon><MkIcon name="icon_external_outlined" /></template>
<span>{{ label }}</span>
</MkDropdownItem>
</template>
Loading