Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
M
model_analysis
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
model_analysis
Commits
50992ed4
Commit
50992ed4
authored
Aug 25, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: restore grouped application switcher
parent
48f2819d
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
82 additions
and
6 deletions
+82
-6
ui-architecture.md
docs/ui-architecture.md
+1
-1
pnpm-workspace.yaml
pnpm-workspace.yaml
+3
-3
XinyuanModal.jsx
src/components/XinyuanModal.jsx
+1
-0
ApplicationSwitcherDialog.jsx
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
+67
-2
Layout.jsx
src/pages/project_manager_xy/Layout.jsx
+3
-0
model-monitor.business-flow.md
src/pages/project_manager_xy/model-monitor.business-flow.md
+7
-0
No files found.
docs/ui-architecture.md
View file @
50992ed4
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
## 组件边界
## 组件边界
-
`Layout.jsx`
:HeroUI Pro Sidebar/Navbar、主题切换、应用切换、退出。
-
`Layout.jsx`
:HeroUI Pro Sidebar/Navbar、主题切换、应用切换、退出。
-
`ApplicationSwitcherDialog.jsx`
:HeroUI Modal/Card/Surface/Avatar。
-
`ApplicationSwitcherDialog.jsx`
:HeroUI Modal/Card/Surface/Avatar
;从
`SiteContext`
的动态
`site.group`
构建应用分组,不在 UI 层写死分组数据
。
-
`XinyuanModal.jsx`
:HeroUI
`Modal.Root > Backdrop > Container > Dialog`
的业务无关组合,集中实现 320–1216px 设计系统宽度梯度和 28px 标准内容 inset;扩展尺寸由官方
`lg`
Container 加共享最大宽度实现,不向 HeroUI v3 传递无效的
`xl/4xl`
size。圆角、背景、阴影和交互状态仍由 HeroUI 官方 Modal 样式负责。
-
`XinyuanModal.jsx`
:HeroUI
`Modal.Root > Backdrop > Container > Dialog`
的业务无关组合,集中实现 320–1216px 设计系统宽度梯度和 28px 标准内容 inset;扩展尺寸由官方
`lg`
Container 加共享最大宽度实现,不向 HeroUI v3 传递无效的
`xl/4xl`
size。圆角、背景、阴影和交互状态仍由 HeroUI 官方 Modal 样式负责。
-
`ModelMonitorShared.jsx`
:业务中性的 SearchField、DataGrid、Pagination、ActionBar、状态 Chip、动态字段组合。
-
`ModelMonitorShared.jsx`
:业务中性的 SearchField、DataGrid、Pagination、ActionBar、状态 Chip、动态字段组合。
-
`XinyuanAutocomplete.jsx`
:HeroUI Autocomplete/SearchField/ListBox 组合,暴露当前 HeroUI 值契约,不兼容旧 Select props。
-
`XinyuanAutocomplete.jsx`
:HeroUI Autocomplete/SearchField/ListBox 组合,暴露当前 HeroUI 值契约,不兼容旧 Select props。
...
...
pnpm-workspace.yaml
View file @
50992ed4
onlyBuiltDependencie
s
:
allowBuild
s
:
-
'
@heroui-pro/react'
"
@heroui-pro/react"
:
true
-
'
@zowe/secrets-for-zowe-sdk'
"
@zowe/secrets-for-zowe-sdk"
:
true
src/components/XinyuanModal.jsx
View file @
50992ed4
...
@@ -34,6 +34,7 @@ export default function XinyuanModal({
...
@@ -34,6 +34,7 @@ export default function XinyuanModal({
>
>
<
Modal
.
Container
placement=
"center"
scroll=
"inside"
size=
{
modalSize
}
>
<
Modal
.
Container
placement=
"center"
scroll=
"inside"
size=
{
modalSize
}
>
<
Modal
.
Dialog
<
Modal
.
Dialog
aria
-
label=
{
title
}
className=
{
clsx
(
className=
{
clsx
(
'xy-modal-dialog p-7'
,
'xy-modal-dialog p-7'
,
DIALOG_WIDTH_CLASS
[
size
],
DIALOG_WIDTH_CLASS
[
size
],
...
...
src/pages/project_manager_xy/ApplicationSwitcherDialog.jsx
View file @
50992ed4
...
@@ -44,6 +44,47 @@ function mergeApplications(fetched, fallbacks) {
...
@@ -44,6 +44,47 @@ function mergeApplications(fetched, fallbacks) {
return
[...
merged
.
values
()].
sort
((
left
,
right
)
=>
left
.
order
-
right
.
order
||
left
.
title
.
localeCompare
(
right
.
title
,
'zh-CN'
));
return
[...
merged
.
values
()].
sort
((
left
,
right
)
=>
left
.
order
-
right
.
order
||
left
.
title
.
localeCompare
(
right
.
title
,
'zh-CN'
));
}
}
function
buildApplicationSections
(
applications
,
applicationGroups
)
{
if
(
!
Array
.
isArray
(
applicationGroups
)
||
applicationGroups
.
length
===
0
)
{
return
[{
key
:
'all'
,
label
:
'全部应用'
,
applications
}];
}
const
applicationMap
=
new
Map
(
applications
.
map
((
application
)
=>
[
application
.
name
,
application
]));
const
groupedApplicationNames
=
new
Set
();
const
sections
=
applicationGroups
.
map
((
group
,
index
)
=>
{
const
groupApplications
=
Array
.
isArray
(
group
?.
applications
)
?
group
.
applications
.
map
((
name
)
=>
{
const
application
=
applicationMap
.
get
(
name
);
if
(
application
)
groupedApplicationNames
.
add
(
name
);
return
application
;
})
.
filter
(
Boolean
)
:
[];
return
{
key
:
group
?.
label
||
`group-
${
index
}
`
,
label
:
group
?.
label
||
'应用分组'
,
applications
:
groupApplications
,
};
})
.
filter
((
section
)
=>
section
.
applications
.
length
>
0
);
const
ungroupedApplications
=
applications
.
filter
(
(
application
)
=>
!
groupedApplicationNames
.
has
(
application
.
name
),
);
if
(
ungroupedApplications
.
length
>
0
)
{
sections
.
push
({
key
:
'ungrouped'
,
label
:
'其他应用'
,
applications
:
ungroupedApplications
,
});
}
return
sections
;
}
function
isCurrentApplication
(
application
)
{
function
isCurrentApplication
(
application
)
{
if
(
application
.
name
===
APP_ROUTE_NAME
)
return
true
;
if
(
application
.
name
===
APP_ROUTE_NAME
)
return
true
;
if
(
typeof
window
===
'undefined'
)
return
false
;
if
(
typeof
window
===
'undefined'
)
return
false
;
...
@@ -87,12 +128,16 @@ function LoadingSkeleton() {
...
@@ -87,12 +128,16 @@ function LoadingSkeleton() {
);
);
}
}
export
default
function
ApplicationSwitcherDialog
({
isOpen
,
onOpenChange
,
userInfo
})
{
export
default
function
ApplicationSwitcherDialog
({
isOpen
,
onOpenChange
,
siteInfo
,
userInfo
})
{
const
fallbacks
=
useMemo
(()
=>
(
Array
.
isArray
(
userInfo
?.
applications
)
?
userInfo
.
applications
:
[]),
[
userInfo
]);
const
fallbacks
=
useMemo
(()
=>
(
Array
.
isArray
(
userInfo
?.
applications
)
?
userInfo
.
applications
:
[]),
[
userInfo
]);
const
[
applications
,
setApplications
]
=
useState
(()
=>
mergeApplications
([],
fallbacks
));
const
[
applications
,
setApplications
]
=
useState
(()
=>
mergeApplications
([],
fallbacks
));
const
[
status
,
setStatus
]
=
useState
(
'idle'
);
const
[
status
,
setStatus
]
=
useState
(
'idle'
);
const
[
errorMessage
,
setErrorMessage
]
=
useState
(
''
);
const
[
errorMessage
,
setErrorMessage
]
=
useState
(
''
);
const
currentApplicationName
=
useMemo
(()
=>
applications
.
find
(
isCurrentApplication
)?.
name
||
APP_ROUTE_NAME
,
[
applications
]);
const
currentApplicationName
=
useMemo
(()
=>
applications
.
find
(
isCurrentApplication
)?.
name
||
APP_ROUTE_NAME
,
[
applications
]);
const
applicationSections
=
useMemo
(
()
=>
buildApplicationSections
(
applications
,
siteInfo
?.
group
),
[
applications
,
siteInfo
?.
group
],
);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
isOpen
)
return
undefined
;
if
(
!
isOpen
)
return
undefined
;
...
@@ -122,7 +167,27 @@ export default function ApplicationSwitcherDialog({ isOpen, onOpenChange, userIn
...
@@ -122,7 +167,27 @@ export default function ApplicationSwitcherDialog({ isOpen, onOpenChange, userIn
onOpenChange=
{
onOpenChange
}
onOpenChange=
{
onOpenChange
}
>
>
{
status
===
'error'
&&
errorMessage
?
<
Alert
status=
"warning"
><
Alert
.
Indicator
><
WarningOutlined
className=
"size-5"
/></
Alert
.
Indicator
><
Alert
.
Content
><
Alert
.
Description
>
{
errorMessage
}
</
Alert
.
Description
></
Alert
.
Content
></
Alert
>
:
null
}
{
status
===
'error'
&&
errorMessage
?
<
Alert
status=
"warning"
><
Alert
.
Indicator
><
WarningOutlined
className=
"size-5"
/></
Alert
.
Indicator
><
Alert
.
Content
><
Alert
.
Description
>
{
errorMessage
}
</
Alert
.
Description
></
Alert
.
Content
></
Alert
>
:
null
}
{
status
===
'loading'
&&
applications
.
length
===
0
?
<
LoadingSkeleton
/>
:
applications
.
length
?
<
div
className=
"grid gap-3 sm:grid-cols-2"
>
{
applications
.
map
((
application
)
=>
<
ApplicationCard
application=
{
application
}
current=
{
application
.
name
===
currentApplicationName
}
key=
{
application
.
name
}
/>)
}
</
div
>
:
<
EmptyState
size=
"md"
><
EmptyState
.
Header
><
EmptyState
.
Title
>
暂无可切换应用
</
EmptyState
.
Title
><
EmptyState
.
Description
>
当前账号还没有分配其它应用权限。
</
EmptyState
.
Description
></
EmptyState
.
Header
></
EmptyState
>
}
{
status
===
'loading'
&&
applications
.
length
===
0
?
<
LoadingSkeleton
/>
:
applications
.
length
?
(
<
div
className=
"space-y-6"
>
{
applicationSections
.
map
((
section
)
=>
(
<
section
key=
{
section
.
key
}
>
<
div
className=
"mb-3 flex items-center justify-between gap-3"
>
<
h3
className=
"text-sm font-medium leading-5"
>
{
section
.
label
}
</
h3
>
<
Description
>
{
section
.
applications
.
length
}
个应用
</
Description
>
</
div
>
<
div
className=
"grid gap-3 sm:grid-cols-2"
>
{
section
.
applications
.
map
((
application
)
=>
(
<
ApplicationCard
application=
{
application
}
current=
{
application
.
name
===
currentApplicationName
}
key=
{
application
.
name
}
/>
))
}
</
div
>
</
section
>
))
}
</
div
>
)
:
<
EmptyState
size=
"md"
><
EmptyState
.
Header
><
EmptyState
.
Title
>
暂无可切换应用
</
EmptyState
.
Title
><
EmptyState
.
Description
>
当前账号还没有分配其它应用权限。
</
EmptyState
.
Description
></
EmptyState
.
Header
></
EmptyState
>
}
</
XinyuanModal
>
</
XinyuanModal
>
);
);
}
}
src/pages/project_manager_xy/Layout.jsx
View file @
50992ed4
...
@@ -10,6 +10,7 @@ import {
...
@@ -10,6 +10,7 @@ import {
import
ApplicationSwitcherDialog
from
'@/pages/project_manager_xy/ApplicationSwitcherDialog'
;
import
ApplicationSwitcherDialog
from
'@/pages/project_manager_xy/ApplicationSwitcherDialog'
;
import
SidebarPanelIcon
from
'@/components/SidebarPanelIcon'
;
import
SidebarPanelIcon
from
'@/components/SidebarPanelIcon'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
UserContext
}
from
'@/wrapper/Auth'
;
import
{
SiteContext
}
from
'@/wrapper/Site'
;
import
{
import
{
AppstoreOutlined
,
AppstoreOutlined
,
DatabaseOutlined
,
DatabaseOutlined
,
...
@@ -136,6 +137,7 @@ function SidebarContents({ pathname, userInfo, onOpenApplicationSwitcher }) {
...
@@ -136,6 +137,7 @@ function SidebarContents({ pathname, userInfo, onOpenApplicationSwitcher }) {
export
default
function
ProjectManagerLayout
()
{
export
default
function
ProjectManagerLayout
()
{
const
location
=
useLocation
();
const
location
=
useLocation
();
const
userInfo
=
useContext
(
UserContext
);
const
userInfo
=
useContext
(
UserContext
);
const
siteInfo
=
useContext
(
SiteContext
);
const
[
theme
,
setTheme
]
=
useState
(
getInitialTheme
);
const
[
theme
,
setTheme
]
=
useState
(
getInitialTheme
);
const
[
isApplicationDialogOpen
,
setIsApplicationDialogOpen
]
=
useState
(
false
);
const
[
isApplicationDialogOpen
,
setIsApplicationDialogOpen
]
=
useState
(
false
);
const
pageTitle
=
useMemo
(()
=>
getRouteTitle
(
location
.
pathname
),
[
location
.
pathname
]);
const
pageTitle
=
useMemo
(()
=>
getRouteTitle
(
location
.
pathname
),
[
location
.
pathname
]);
...
@@ -203,6 +205,7 @@ export default function ProjectManagerLayout() {
...
@@ -203,6 +205,7 @@ export default function ProjectManagerLayout() {
<
ApplicationSwitcherDialog
<
ApplicationSwitcherDialog
isOpen=
{
isApplicationDialogOpen
}
isOpen=
{
isApplicationDialogOpen
}
siteInfo=
{
siteInfo
}
userInfo=
{
userInfo
}
userInfo=
{
userInfo
}
onOpenChange=
{
setIsApplicationDialogOpen
}
onOpenChange=
{
setIsApplicationDialogOpen
}
/>
/>
...
...
src/pages/project_manager_xy/model-monitor.business-flow.md
View file @
50992ed4
...
@@ -20,6 +20,13 @@
...
@@ -20,6 +20,13 @@
旧
`/analysis/overview`
、
`/analysis/views`
、
`/analysis/forwards`
、
`/analysis/boards`
仅用于重定向,不再加载旧分析页面。
旧
`/analysis/overview`
、
`/analysis/views`
、
`/analysis/forwards`
、
`/analysis/boards`
仅用于重定向,不再加载旧分析页面。
### 2.1 应用切换
-
侧栏应用切换入口打开 HeroUI 应用切换弹窗,不改变当前模型监控路由和登录状态。
-
可访问应用继续由
`GET /application/info`
与当前登录用户的
`applications`
合并得到,保留原排序、回退和跳转规则。
-
应用分组从
`GET /info`
返回的
`site.group`
动态读取;每个分组通过
`applications`
中的应用
`name`
关联,前端不写死分组名称或成员。
-
没有动态分组配置时展示“全部应用”;存在动态分组时,未被任何分组引用的可访问应用展示在“其他应用”中,空分组不渲染。
## 3. 外部数据源契约
## 3. 外部数据源契约
当前外部来源仍使用 Mock 适配器,但返回结构已映射到
`ANALYZE_MXVIEW_INFO`
和
`ANALYZE_MODEL_RUN_BATCH_INFO`
。UI 仅依赖以下方法,真实 Oracle 适配器接入时不得修改页面调用:
当前外部来源仍使用 Mock 适配器,但返回结构已映射到
`ANALYZE_MXVIEW_INFO`
和
`ANALYZE_MODEL_RUN_BATCH_INFO`
。UI 仅依赖以下方法,真实 Oracle 适配器接入时不得修改页面调用:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment