Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
problem_rectification
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
problem_rectification
Commits
5206d42b
Commit
5206d42b
authored
Aug 25, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: align application switcher with Xinyuan standard
parent
0b9490a0
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
133 additions
and
28 deletions
+133
-28
ApplicationSwitcherDialog.jsx
...pages/problem_rectification/ApplicationSwitcherDialog.jsx
+133
-28
No files found.
src/pages/problem_rectification/ApplicationSwitcherDialog.jsx
View file @
5206d42b
import
xinyuanLogo
from
'@/assets/xy-logo-red.png'
;
import
{
APPLICATION_BASE_PATH
,
APPLICATION_DISPLAY_NAME
,
}
from
'@/config/application'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
getApplications
}
from
'@/services/DataService'
;
import
{
Close
,
ExclamationTriangleIcon
}
from
'@/components/AppIcons'
;
import
{
ExclamationTriangleIcon
}
from
'@/components/AppIcons'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
EmptyState
}
from
'@heroui-pro/react/empty-state'
;
import
{
import
{
Alert
,
Alert
,
...
@@ -13,6 +18,23 @@ import {
...
@@ -13,6 +18,23 @@ import {
}
from
'@heroui/react'
;
}
from
'@heroui/react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
const
CURRENT_APPLICATION_NAME
=
APPLICATION_BASE_PATH
.
replace
(
/^
\/
+|
\/
+$/g
,
''
);
function
unwrapApplications
(
response
)
{
const
candidates
=
[
response
,
response
?.
data
,
response
?.
applications
,
response
?.
apps
,
response
?.
records
,
response
?.
rows
,
response
?.
list
,
response
?.
result
,
];
return
candidates
.
find
(
Array
.
isArray
)
||
[];
}
function
normalizeLabelList
(
items
)
{
function
normalizeLabelList
(
items
)
{
if
(
!
Array
.
isArray
(
items
))
{
if
(
!
Array
.
isArray
(
items
))
{
return
[];
return
[];
...
@@ -39,17 +61,20 @@ function resolveApplicationUrl(application) {
...
@@ -39,17 +61,20 @@ function resolveApplicationUrl(application) {
function
normalizeApplication
(
application
,
fallback
=
{})
{
function
normalizeApplication
(
application
,
fallback
=
{})
{
const
name
=
application
?.
name
||
fallback
?.
name
||
''
;
const
name
=
application
?.
name
||
fallback
?.
name
||
''
;
const
current
=
name
===
CURRENT_APPLICATION_NAME
;
return
{
return
{
name
,
name
,
title
:
application
?.
title
||
fallback
?.
title
||
application
?.
name
||
fallback
?.
name
||
'未命名应用'
,
title
:
current
?
APPLICATION_DISPLAY_NAME
:
application
?.
title
||
fallback
?.
title
||
application
?.
name
||
fallback
?.
name
||
'未命名应用'
,
description
:
description
:
application
?.
description
||
application
?.
description
||
fallback
?.
description
||
fallback
?.
description
||
fallback
?.
title
||
fallback
?.
title
||
application
?.
title
||
application
?.
title
||
'进入对应应用继续处理业务'
,
'进入对应应用继续处理业务'
,
logo
:
application
?.
logo
||
fallback
?.
logo
||
null
,
logo
:
current
?
xinyuanLogo
:
application
?.
logo
||
fallback
?.
logo
||
null
,
url
:
resolveApplicationUrl
({
url
:
resolveApplicationUrl
({
name
,
name
,
}),
}),
...
@@ -85,6 +110,15 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
...
@@ -85,6 +110,15 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
mergedMap
.
set
(
application
.
name
,
normalizeApplication
(
application
,
application
));
mergedMap
.
set
(
application
.
name
,
normalizeApplication
(
application
,
application
));
});
});
const
currentApplication
=
mergedMap
.
get
(
CURRENT_APPLICATION_NAME
);
mergedMap
.
set
(
CURRENT_APPLICATION_NAME
,
normalizeApplication
(
{
name
:
CURRENT_APPLICATION_NAME
},
currentApplication
||
{
title
:
APPLICATION_DISPLAY_NAME
},
),
);
return
Array
.
from
(
mergedMap
.
values
()).
sort
((
left
,
right
)
=>
{
return
Array
.
from
(
mergedMap
.
values
()).
sort
((
left
,
right
)
=>
{
const
orderDifference
=
Number
(
left
.
order
||
0
)
-
Number
(
right
.
order
||
0
);
const
orderDifference
=
Number
(
left
.
order
||
0
)
-
Number
(
right
.
order
||
0
);
if
(
orderDifference
!==
0
)
{
if
(
orderDifference
!==
0
)
{
...
@@ -95,6 +129,60 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
...
@@ -95,6 +129,60 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
});
});
}
}
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
(
typeof
window
===
'undefined'
)
{
if
(
typeof
window
===
'undefined'
)
{
return
false
;
return
false
;
...
@@ -151,12 +239,12 @@ function ApplicationCard({ application, current }) {
...
@@ -151,12 +239,12 @@ function ApplicationCard({ application, current }) {
</
Avatar
>
</
Avatar
>
</
Surface
>
</
Surface
>
<
span
className=
"min-w-0 flex-1"
>
<
span
className=
"min-w-0 flex-1"
>
<
span
className=
"block
break-words
font-medium leading-5 text-foreground"
title=
{
application
.
title
}
>
<
span
className=
"block
truncate text-sm
font-medium leading-5 text-foreground"
title=
{
application
.
title
}
>
{
application
.
title
}
{
application
.
title
}
</
span
>
</
span
>
<
span
className=
"block truncate text-sm text-muted
"
title=
{
application
.
description
}
>
<
Description
className=
"mt-0.5 block truncate leading-4
"
title=
{
application
.
description
}
>
{
application
.
description
||
application
.
name
||
'应用'
}
{
application
.
description
||
application
.
name
||
'应用'
}
</
spa
n
>
</
Descriptio
n
>
</
span
>
</
span
>
{
current
?
<
Chip
color=
"accent"
size=
"sm"
>
当前
</
Chip
>
:
null
}
{
current
?
<
Chip
color=
"accent"
size=
"sm"
>
当前
</
Chip
>
:
null
}
</
a
>
</
a
>
...
@@ -186,6 +274,7 @@ function LoadingSkeleton() {
...
@@ -186,6 +274,7 @@ function LoadingSkeleton() {
export
default
function
ApplicationSwitcherDialog
({
export
default
function
ApplicationSwitcherDialog
({
open
,
open
,
onClose
,
onClose
,
siteInfo
,
userInfo
,
userInfo
,
})
{
})
{
const
fallbackApplications
=
useMemo
(()
=>
{
const
fallbackApplications
=
useMemo
(()
=>
{
...
@@ -203,7 +292,9 @@ export default function ApplicationSwitcherDialog({
...
@@ -203,7 +292,9 @@ export default function ApplicationSwitcherDialog({
.
filter
((
application
)
=>
application
.
name
);
.
filter
((
application
)
=>
application
.
name
);
},
[
userInfo
]);
},
[
userInfo
]);
const
[
applications
,
setApplications
]
=
useState
(
fallbackApplications
);
const
[
applications
,
setApplications
]
=
useState
(()
=>
mergeApplications
([],
fallbackApplications
),
);
const
[
status
,
setStatus
]
=
useState
(
const
[
status
,
setStatus
]
=
useState
(
fallbackApplications
.
length
>
0
?
'success'
:
'idle'
,
fallbackApplications
.
length
>
0
?
'success'
:
'idle'
,
);
);
...
@@ -211,19 +302,19 @@ export default function ApplicationSwitcherDialog({
...
@@ -211,19 +302,19 @@ export default function ApplicationSwitcherDialog({
const
currentApplicationName
=
useMemo
(()
=>
{
const
currentApplicationName
=
useMemo
(()
=>
{
return
applications
.
find
(
isCurrentApplication
)?.
name
||
''
;
return
applications
.
find
(
isCurrentApplication
)?.
name
||
''
;
},
[
applications
]);
},
[
applications
]);
const
applicationSections
=
useMemo
(
()
=>
buildApplicationSections
(
applications
,
siteInfo
?.
group
),
[
applications
,
siteInfo
?.
group
],
);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
fallbackApplications
.
length
===
0
)
{
if
(
fallbackApplications
.
length
===
0
)
{
return
;
return
;
}
}
setApplications
((
previous
)
=>
{
setApplications
((
previous
)
=>
if
(
previous
.
length
>
0
)
{
mergeApplications
(
previous
,
fallbackApplications
),
return
previous
;
);
}
return
fallbackApplications
;
});
},
[
fallbackApplications
]);
},
[
fallbackApplications
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
...
@@ -239,7 +330,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -239,7 +330,7 @@ export default function ApplicationSwitcherDialog({
try
{
try
{
const
response
=
await
getApplications
();
const
response
=
await
getApplications
();
const
fetchedApplications
=
Array
.
isArray
(
response
)
?
response
:
[]
;
const
fetchedApplications
=
unwrapApplications
(
response
)
;
if
(
cancelled
)
{
if
(
cancelled
)
{
return
;
return
;
...
@@ -257,7 +348,7 @@ export default function ApplicationSwitcherDialog({
...
@@ -257,7 +348,7 @@ export default function ApplicationSwitcherDialog({
return
;
return
;
}
}
setApplications
(
fallbackApplications
);
setApplications
(
mergeApplications
([],
fallbackApplications
)
);
setStatus
(
'error'
);
setStatus
(
'error'
);
setErrorMessage
(
'应用列表加载失败,已回退展示当前账号上的应用信息。'
);
setErrorMessage
(
'应用列表加载失败,已回退展示当前账号上的应用信息。'
);
}
}
...
@@ -282,21 +373,21 @@ export default function ApplicationSwitcherDialog({
...
@@ -282,21 +373,21 @@ export default function ApplicationSwitcherDialog({
>
>
<
Modal
.
Container
className=
"w-full sm:w-full"
placement=
"center"
scroll=
"inside"
size=
"lg"
>
<
Modal
.
Container
className=
"w-full sm:w-full"
placement=
"center"
scroll=
"inside"
size=
"lg"
>
<
Modal
.
Dialog
<
Modal
.
Dialog
className=
"max-h-[min(42rem,calc(100dvh-2rem))]"
className=
"max-h-[min(42rem,calc(100dvh-2rem))] p-7"
style=
{
{
maxWidth
:
'min(46rem, calc(100vw - 2rem))'
}
}
style=
{
{
maxWidth
:
'min(46rem, calc(100vw - 2rem))'
,
width
:
'min(46rem, calc(100vw - 2rem))'
,
}
}
>
>
<
Modal
.
CloseTrigger
aria
-
label=
"关闭"
>
<
Modal
.
CloseTrigger
<
Close
size=
{
16
}
/>
aria
-
label=
"关闭"
</
Modal
.
CloseTrigger
>
style=
{
{
insetInlineEnd
:
'1.75rem'
,
top
:
'1.75rem'
}
}
/>
<
Modal
.
Header
>
<
Modal
.
Header
>
<
div
className=
"min-w-0"
>
<
Modal
.
Heading
>
应用切换
</
Modal
.
Heading
>
<
Modal
.
Heading
>
应用切换
</
Modal
.
Heading
>
<
Description
>
<
div
className=
"text-sm text-muted"
>
选择你可访问的应用
</
div
>
选择你可访问的应用
</
Description
>
</
div
>
</
Modal
.
Header
>
</
Modal
.
Header
>
<
Modal
.
Body
>
<
Modal
.
Body
style=
{
{
marginTop
:
'0.75rem'
}
}
>
{
status
===
'error'
&&
errorMessage
?
(
{
status
===
'error'
&&
errorMessage
?
(
<
Alert
status=
"warning"
>
<
Alert
status=
"warning"
>
<
Alert
.
Indicator
><
ExclamationTriangleIcon
size=
{
20
}
/></
Alert
.
Indicator
>
<
Alert
.
Indicator
><
ExclamationTriangleIcon
size=
{
20
}
/></
Alert
.
Indicator
>
...
@@ -307,8 +398,19 @@ export default function ApplicationSwitcherDialog({
...
@@ -307,8 +398,19 @@ export default function ApplicationSwitcherDialog({
{
status
===
'loading'
&&
applications
.
length
===
0
?
(
{
status
===
'loading'
&&
applications
.
length
===
0
?
(
<
LoadingSkeleton
/>
<
LoadingSkeleton
/>
)
:
applications
.
length
>
0
?
(
)
:
applications
.
length
>
0
?
(
<
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 text-foreground"
>
{
section
.
label
}
</
h3
>
<
Description
>
{
section
.
applications
.
length
}
个应用
</
Description
>
</
div
>
<
div
className=
"grid gap-3 sm:grid-cols-2"
>
<
div
className=
"grid gap-3 sm:grid-cols-2"
>
{
applications
.
map
((
application
)
=>
(
{
section
.
applications
.
map
((
application
)
=>
(
<
ApplicationCard
<
ApplicationCard
key=
{
application
.
name
}
key=
{
application
.
name
}
application=
{
application
}
application=
{
application
}
...
@@ -316,6 +418,9 @@ export default function ApplicationSwitcherDialog({
...
@@ -316,6 +418,9 @@ export default function ApplicationSwitcherDialog({
/>
/>
))
}
))
}
</
div
>
</
div
>
</
section
>
))
}
</
div
>
)
:
(
)
:
(
<
EmptyState
size=
"md"
>
<
EmptyState
size=
"md"
>
<
EmptyState
.
Header
>
<
EmptyState
.
Header
>
...
...
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