Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
088eacea
提交
088eacea
authored
8月 10, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 添加菜单路径处理和高级筛选功能,优化布局样式
上级
12993d55
隐藏空白字符变更
内嵌
并排
正在显示
18 个修改的文件
包含
174 行增加
和
66 行删除
+174
-66
menu.utils.js
apps/admin/src/components/layout/components/menu.utils.js
+18
-0
menu.utils.test.js
...admin/src/components/layout/components/menu.utils.test.js
+20
-1
index.jsx
apps/admin/src/components/layout/index.jsx
+18
-1
index.less
apps/admin/src/components/layout/index.less
+8
-1
LibraryToolbar.tsx
...earning/src/modules/library/components/LibraryToolbar.tsx
+82
-63
LibraryView.css
apps/learning/src/modules/library/views/LibraryView.css
+16
-0
LibraryView.tsx
apps/learning/src/modules/library/views/LibraryView.tsx
+1
-0
MyBooksView.tsx
apps/learning/src/modules/my/books/views/MyBooksView.tsx
+1
-0
MyBookshelfView.tsx
...arning/src/modules/my/bookshelf/views/MyBookshelfView.tsx
+1
-0
MyCouponsView.tsx
apps/learning/src/modules/my/coupons/views/MyCouponsView.tsx
+1
-0
MyDiscussionsView.tsx
...ng/src/modules/my/discussions/views/MyDiscussionsView.tsx
+1
-0
MyFavoritesView.tsx
...arning/src/modules/my/favorites/views/MyFavoritesView.tsx
+1
-0
MyMessagesView.tsx
...learning/src/modules/my/messages/views/MyMessagesView.tsx
+1
-0
MyNotesView.tsx
apps/learning/src/modules/my/notes/views/MyNotesView.tsx
+1
-0
MyOrdersView.tsx
apps/learning/src/modules/my/orders/views/MyOrdersView.tsx
+1
-0
MyWalletView.tsx
apps/learning/src/modules/my/wallet/views/MyWalletView.tsx
+1
-0
MyWrongQuestionsView.tsx
...modules/my/wrong-questions/views/MyWrongQuestionsView.tsx
+1
-0
SearchView.tsx
apps/learning/src/modules/search/views/SearchView.tsx
+1
-0
没有找到文件。
apps/admin/src/components/layout/components/menu.utils.js
浏览文件 @
088eacea
...
...
@@ -42,3 +42,21 @@ export function getOpenMenuKeys(menuList, pathname) {
visit
(
menuList
)
return
openKeys
}
export
function
getFirstMenuPath
(
menuList
=
[])
{
for
(
const
item
of
menuList
)
{
if
(
item
.
children
?.
length
)
{
const
childPath
=
getFirstMenuPath
(
item
.
children
)
if
(
childPath
)
return
childPath
}
if
(
item
.
path
&&
item
.
path
!==
'/'
)
return
item
.
path
}
return
''
}
export
function
hasMenuPath
(
menuList
=
[],
pathname
)
{
return
menuList
.
some
(
(
item
)
=>
item
.
path
===
pathname
||
(
item
.
children
?.
length
&&
hasMenuPath
(
item
.
children
,
pathname
)),
)
}
apps/admin/src/components/layout/components/menu.utils.test.js
浏览文件 @
088eacea
import
test
from
'node:test'
import
assert
from
'node:assert/strict'
import
{
getOpenMenuKeys
,
getSelectedMenuKeys
}
from
'./menu.utils.js'
import
{
getFirstMenuPath
,
getOpenMenuKeys
,
getSelectedMenuKeys
,
hasMenuPath
,
}
from
'./menu.utils.js'
test
(
'highlights the current nested menu item'
,
()
=>
{
const
menuList
=
[
...
...
@@ -28,3 +33,17 @@ test('opens a parent with no route when one of its children matches', () => {
assert
.
deepEqual
(
getOpenMenuKeys
(
menuList
,
'/settings/roles'
),
[
'settings'
])
})
test
(
'finds the first accessible leaf menu path'
,
()
=>
{
const
menuList
=
[
{
key
:
'books'
,
path
:
'/books'
,
children
:
[{
key
:
'orders'
,
path
:
'/books/order-management'
}],
},
]
assert
.
equal
(
getFirstMenuPath
(
menuList
),
'/books/order-management'
)
assert
.
equal
(
hasMenuPath
(
menuList
,
'/books/management'
),
false
)
assert
.
equal
(
hasMenuPath
(
menuList
,
'/books/order-management'
),
true
)
})
apps/admin/src/components/layout/index.jsx
浏览文件 @
088eacea
import
{
useState
,
useEffect
,
Suspense
}
from
'react'
import
{
Button
,
Layout
,
Flex
,
Tooltip
}
from
'antd'
import
{
Outlet
,
Link
,
useLocation
}
from
'react-router'
import
{
Outlet
,
Link
,
useLocation
,
useNavigate
}
from
'react-router'
import
{
RiAdvertisementLine
,
RiAppStoreLine
,
...
...
@@ -46,6 +46,7 @@ import Message from './components/message'
import
User
from
'./components/user'
import
Logout
from
'./components/logout'
import
Breadcrumb
from
'./components/breadcrumb'
import
{
getFirstMenuPath
,
hasMenuPath
}
from
'./components/menu.utils'
import
'./index.less'
import
logo
from
'@/assets/images/logo.png'
...
...
@@ -170,6 +171,7 @@ const pageHeaderOverrides = {
export
default
function
AppLayout
()
{
const
setOperationPermissionsList
=
useUserStore
((
state
)
=>
state
.
setOperationPermissionsList
)
const
{
pathname
}
=
useLocation
()
const
navigate
=
useNavigate
()
const
[
menuList
,
setMenuList
]
=
useState
([])
const
[
collapsed
,
setCollapsed
]
=
useState
(
false
)
const
headerOverride
=
pageHeaderOverrides
[
pathname
]
...
...
@@ -206,6 +208,21 @@ export default function AppLayout() {
})()
},
[])
useEffect
(()
=>
{
if
(
menuList
.
length
)
setCollapsed
(
menuList
.
length
<
3
)
},
[
menuList
.
length
])
useEffect
(()
=>
{
if
(
pathname
!==
'/books/management'
||
!
menuList
.
length
||
hasMenuPath
(
menuList
,
pathname
))
{
return
}
const
firstMenuPath
=
getFirstMenuPath
(
menuList
)
if
(
firstMenuPath
&&
firstMenuPath
!==
pathname
)
{
navigate
(
firstMenuPath
,
{
replace
:
true
})
}
},
[
menuList
,
navigate
,
pathname
])
return
(
<
Layout
className=
'layout-container'
>
<
EditorIconSprite
/>
...
...
apps/admin/src/components/layout/index.less
浏览文件 @
088eacea
.layout-container {
height: 100vh;
min-height: 100vh;
overflow: hidden;
background: #fff;
}
...
...
@@ -242,6 +244,9 @@
.layout-main {
min-width: 0;
min-height: 0;
height: 100vh;
overflow: hidden;
background: #fff;
}
...
...
@@ -320,7 +325,9 @@
}
.layout-content {
min-height: calc(100vh - 84px);
min-height: 0;
height: calc(100vh - 84px);
overflow-y: auto;
margin: 0;
padding: 24px 32px;
background: #fff;
...
...
apps/learning/src/modules/library/components/LibraryToolbar.tsx
浏览文件 @
088eacea
...
...
@@ -27,7 +27,14 @@ export function LibraryToolbar({
onReset
,
}:
LibraryToolbarProps
)
{
const
[
keyword
,
setKeyword
]
=
useState
(
state
.
keyword
)
const
[
showAdvancedFilters
,
setShowAdvancedFilters
]
=
useState
(
false
)
const
isSearching
=
Boolean
(
state
.
keyword
)
const
hasAdvancedFilters
=
state
.
category
!==
0
||
state
.
label
!==
0
||
state
.
price
!==
'all'
||
state
.
sortField
!==
'none'
||
state
.
sortOrder
!==
'desc'
const
withAllOption
=
(
filters
:
LibraryFilter
[])
=>
filters
.
some
((
item
)
=>
item
.
id
===
0
)
?
filters
:
[{
id
:
0
,
name
:
'全部'
},
...
filters
]
const
categoryOptions
=
withAllOption
(
categories
).
map
((
item
)
=>
({
...
...
@@ -38,6 +45,10 @@ export function LibraryToolbar({
useEffect
(()
=>
setKeyword
(
state
.
keyword
),
[
state
.
keyword
])
useEffect
(()
=>
{
if
(
hasAdvancedFilters
)
setShowAdvancedFilters
(
true
)
},
[
hasAdvancedFilters
])
return
(
<
Form
component=
{
false
}
aria
-
label=
'图书馆筛选'
>
<
div
className=
'library-toolbar__controls'
>
...
...
@@ -57,70 +68,10 @@ export function LibraryToolbar({
onSearch=
{
onKeywordSearch
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'分类'
>
<
Select
aria
-
label=
'分类'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
category
}
options=
{
categoryOptions
}
onChange=
{
(
category
)
=>
onFiltersChange
({
category
})
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'标签'
>
<
Select
aria
-
label=
'标签'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
label
}
options=
{
labelOptions
}
onChange=
{
(
label
)
=>
onFiltersChange
({
label
})
}
/>
</
Form
.
Item
>
</
Space
>
<
Space
className=
'library-toolbar__secondary'
size=
'small'
aria
-
label=
'显示与排序'
>
<
Form
.
Item
label=
'价格'
>
<
Select
aria
-
label=
'价格'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
price
}
options=
{
[
{
value
:
'all'
,
label
:
'全部'
},
{
value
:
'paid'
,
label
:
'收费'
},
{
value
:
'free'
,
label
:
'免费'
},
]
}
onChange=
{
(
price
)
=>
onFiltersChange
({
price
})
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'排序'
>
<
Space
.
Compact
>
<
Select
aria
-
label=
'排序'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
sortField
}
options=
{
[
{
value
:
'none'
,
label
:
'默认'
},
{
value
:
'read_num'
,
label
:
'阅读人数'
},
{
value
:
'rating'
,
label
:
'评分'
},
]
}
onChange=
{
(
sortField
)
=>
onSortChange
({
sortField
})
}
/>
<
Select
aria
-
label=
'排序方向'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
sortOrder
}
options=
{
[
{
value
:
'desc'
,
label
:
'降序'
},
{
value
:
'asc'
,
label
:
'升序'
},
]
}
onChange=
{
(
sortOrder
)
=>
onSortChange
({
sortOrder
})
}
/>
</
Space
.
Compact
>
</
Form
.
Item
>
<
Space
className=
'library-toolbar__secondary'
size=
'small'
aria
-
label=
'显示与筛选'
>
{
/* 暂时隐藏筛选入口,筛选逻辑保留 */
}
<
Segmented
aria
-
label=
'图书馆视图'
value=
{
state
.
view
}
...
...
@@ -130,9 +81,77 @@ export function LibraryToolbar({
]
}
onChange=
{
(
value
)
=>
onViewChange
(
value
as
LibrarySearchState
[
'view'
])
}
/>
<
Button
onClick=
{
onReset
}
>
重置
</
Button
>
</
Space
>
</
div
>
{
showAdvancedFilters
?
(
<
div
className=
'library-toolbar__advanced'
>
<
Space
wrap
size=
'middle'
>
<
Form
.
Item
label=
'分类'
>
<
Select
aria
-
label=
'分类'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
category
}
options=
{
categoryOptions
}
onChange=
{
(
category
)
=>
onFiltersChange
({
category
})
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'标签'
>
<
Select
aria
-
label=
'标签'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
label
}
options=
{
labelOptions
}
onChange=
{
(
label
)
=>
onFiltersChange
({
label
})
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'价格'
>
<
Select
aria
-
label=
'价格'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
price
}
options=
{
[
{
value
:
'all'
,
label
:
'全部'
},
{
value
:
'paid'
,
label
:
'收费'
},
{
value
:
'free'
,
label
:
'免费'
},
]
}
onChange=
{
(
price
)
=>
onFiltersChange
({
price
})
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
'排序'
>
<
Space
.
Compact
>
<
Select
aria
-
label=
'排序'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
sortField
}
options=
{
[
{
value
:
'none'
,
label
:
'默认'
},
{
value
:
'read_num'
,
label
:
'阅读人数'
},
{
value
:
'rating'
,
label
:
'评分'
},
]
}
onChange=
{
(
sortField
)
=>
onSortChange
({
sortField
})
}
/>
<
Select
aria
-
label=
'排序方向'
disabled=
{
isSearching
}
popupMatchSelectWidth=
{
false
}
value=
{
state
.
sortOrder
}
options=
{
[
{
value
:
'desc'
,
label
:
'降序'
},
{
value
:
'asc'
,
label
:
'升序'
},
]
}
onChange=
{
(
sortOrder
)
=>
onSortChange
({
sortOrder
})
}
/>
</
Space
.
Compact
>
</
Form
.
Item
>
<
Button
onClick=
{
onReset
}
>
重置
</
Button
>
</
Space
>
</
div
>
)
:
null
}
</
Form
>
)
}
apps/learning/src/modules/library/views/LibraryView.css
浏览文件 @
088eacea
...
...
@@ -98,6 +98,17 @@
background
:
#fafbfc
;
}
.library-toolbar__advanced
{
padding
:
14px
16px
;
border
:
1px
solid
#edf0f4
;
border-radius
:
12px
;
background
:
#fafbfc
;
}
.library-toolbar__advanced
.ant-form-item
{
margin-bottom
:
0
;
}
.library-toolbar__controls
.ant-form-item
{
margin-bottom
:
0
;
}
...
...
@@ -253,6 +264,11 @@
border
:
0
;
background
:
transparent
;
}
.library-toolbar__advanced
{
padding
:
0
;
border
:
0
;
background
:
transparent
;
}
.library-books--grid
{
grid-template-columns
:
1
fr
;
}
...
...
apps/learning/src/modules/library/views/LibraryView.tsx
浏览文件 @
088eacea
...
...
@@ -114,6 +114,7 @@ export function LibraryView() {
current=
{
state
.
page
}
pageSize=
{
state
.
pageSize
}
total=
{
booksQuery
.
data
?.
total
??
0
}
hideOnSinglePage
showSizeChanger=
{
false
}
showTotal=
{
(
total
)
=>
`共 ${total} 本`
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
...
...
apps/learning/src/modules/my/books/views/MyBooksView.tsx
浏览文件 @
088eacea
...
...
@@ -87,6 +87,7 @@ export function MyBooksView() {
coursesQuery
.
data
.
items
.
length
,
coursesQuery
.
data
.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPage
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/bookshelf/views/MyBookshelfView.tsx
浏览文件 @
088eacea
...
...
@@ -193,6 +193,7 @@ export function MyBookshelfView() {
items
.
length
,
bookshelfQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
{
setSelectedCartIds
(
new
Set
())
...
...
apps/learning/src/modules/my/coupons/views/MyCouponsView.tsx
浏览文件 @
088eacea
...
...
@@ -56,6 +56,7 @@ export function MyCouponsView() {
items
.
length
,
couponsQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/discussions/views/MyDiscussionsView.tsx
浏览文件 @
088eacea
...
...
@@ -74,6 +74,7 @@ export function MyDiscussionsView() {
books
.
length
,
discussionsQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPage
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/favorites/views/MyFavoritesView.tsx
浏览文件 @
088eacea
...
...
@@ -111,6 +111,7 @@ export function MyFavoritesView() {
favorites
.
length
,
favoritesQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPage
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/messages/views/MyMessagesView.tsx
浏览文件 @
088eacea
...
...
@@ -83,6 +83,7 @@ export function MyMessagesView() {
items
.
length
,
messagesQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/notes/views/MyNotesView.tsx
浏览文件 @
088eacea
...
...
@@ -83,6 +83,7 @@ export function MyNotesView() {
books
.
length
,
notesQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPage
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/orders/views/MyOrdersView.tsx
浏览文件 @
088eacea
...
...
@@ -88,6 +88,7 @@ export function MyOrdersView() {
items
.
length
,
ordersQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/wallet/views/MyWalletView.tsx
浏览文件 @
088eacea
...
...
@@ -63,6 +63,7 @@ export function MyWalletView() {
records
.
length
,
query
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/my/wrong-questions/views/MyWrongQuestionsView.tsx
浏览文件 @
088eacea
...
...
@@ -74,6 +74,7 @@ export function MyWrongQuestionsView() {
books
.
length
,
wrongQuestionsQuery
.
data
?.
total
,
)
}
hideOnSinglePage
showSizeChanger=
{
false
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPage
(
page
,
pageSize
)
}
/>
...
...
apps/learning/src/modules/search/views/SearchView.tsx
浏览文件 @
088eacea
...
...
@@ -154,6 +154,7 @@ export function SearchView() {
current=
{
state
.
page
}
pageSize=
{
state
.
pageSize
}
total=
{
booksQuery
.
data
.
total
}
hideOnSinglePage
showSizeChanger=
{
false
}
showTotal=
{
(
total
)
=>
`共 ${total} 本`
}
onChange=
{
(
page
,
pageSize
)
=>
void
queryState
.
setPagination
(
page
,
pageSize
)
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论