mall-components 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +128 -0
  2. package/build/_components-raw.css +791 -0
  3. package/build/_shims/antd.js +1 -0
  4. package/build/_shims/icons.js +1 -0
  5. package/build/_shims/moment.js +1 -0
  6. package/build/_shims/react-dom.js +1 -0
  7. package/build/_shims/react.js +1 -0
  8. package/build/adapters/DataSourceAdapter.d.ts +46 -0
  9. package/build/components/AdminLayout/AdminLayout.d.ts +5 -0
  10. package/build/components/AdminLayout/Breadcrumb.d.ts +8 -0
  11. package/build/components/AdminLayout/MainContent.d.ts +17 -0
  12. package/build/components/AdminLayout/Navbar.d.ts +10 -0
  13. package/build/components/AdminLayout/Sidebar.d.ts +14 -0
  14. package/build/components/AdminLayout/TabBar.d.ts +13 -0
  15. package/build/components/AdminLayout/TabPane.d.ts +4 -0
  16. package/build/components/AdminLayout/index.d.ts +3 -0
  17. package/build/components/AdminLayout/types.d.ts +42 -0
  18. package/build/components/CouponCard/CouponCard.d.ts +20 -0
  19. package/build/components/CouponCard/index.d.ts +1 -0
  20. package/build/components/OrderForm/OrderForm.d.ts +18 -0
  21. package/build/components/OrderForm/index.d.ts +1 -0
  22. package/build/components/OrderList/OrderList.d.ts +29 -0
  23. package/build/components/OrderList/index.d.ts +1 -0
  24. package/build/components/ProductForm/ProductForm.d.ts +18 -0
  25. package/build/components/ProductForm/index.d.ts +3 -0
  26. package/build/components/ProductList/ProductList.d.ts +47 -0
  27. package/build/components/ProductList/index.d.ts +3 -0
  28. package/build/components/PromotionCard/PromotionCard.d.ts +22 -0
  29. package/build/components/PromotionCard/index.d.ts +1 -0
  30. package/build/components/RoleCard/RoleCard.d.ts +18 -0
  31. package/build/components/RoleCard/index.d.ts +1 -0
  32. package/build/components/UserCard/UserCard.d.ts +17 -0
  33. package/build/components/UserCard/index.d.ts +1 -0
  34. package/build/entry-meta.d.ts +603 -0
  35. package/build/index.css +1 -0
  36. package/build/index.js +1 -0
  37. package/build/mall-components-meta.js +2563 -0
  38. package/build/mall-components.cdn.umd.css +1 -0
  39. package/build/mall-components.cdn.umd.js +8 -0
  40. package/build/mall-components.codesandbox.combined.js +1094 -0
  41. package/build/mall-components.codesandbox.css +401 -0
  42. package/build/mall-components.codesandbox.js +1080 -0
  43. package/build/mall-components.umd.css +1 -0
  44. package/build/mall-components.umd.js +8 -0
  45. package/build/meta/adminLayoutMeta.d.ts +3 -0
  46. package/build/meta/couponCardMeta.d.ts +128 -0
  47. package/build/meta/icons.d.ts +10 -0
  48. package/build/meta/orderFormMeta.d.ts +111 -0
  49. package/build/meta/orderListMeta.d.ts +170 -0
  50. package/build/meta/productFormMeta.d.ts +3 -0
  51. package/build/meta/productListMeta.d.ts +200 -0
  52. package/build/meta/promotionCardMeta.d.ts +129 -0
  53. package/build/meta/roleCardMeta.d.ts +3 -0
  54. package/build/meta/tabPaneMeta.d.ts +3 -0
  55. package/build/meta/userCardMeta.d.ts +3 -0
  56. package/build/meta.d.ts +605 -0
  57. package/build/setters/RestApiTester.d.ts +11 -0
  58. package/build/types/common.d.ts +17 -0
  59. package/build/types/marketing.d.ts +128 -0
  60. package/build/types/order.d.ts +174 -0
  61. package/build/types/permission.d.ts +101 -0
  62. package/build/types/product.d.ts +47 -0
  63. package/package.json +1 -0
  64. package/src/adapters/DataSourceAdapter.ts +445 -0
  65. package/src/components/AdminLayout/AdminLayout.scss +447 -0
  66. package/src/components/AdminLayout/AdminLayout.tsx +681 -0
  67. package/src/components/AdminLayout/Breadcrumb.tsx +60 -0
  68. package/src/components/AdminLayout/MainContent.tsx +54 -0
  69. package/src/components/AdminLayout/Navbar.tsx +76 -0
  70. package/src/components/AdminLayout/Sidebar.tsx +256 -0
  71. package/src/components/AdminLayout/TabBar.tsx +177 -0
  72. package/src/components/AdminLayout/TabPane.tsx +29 -0
  73. package/src/components/AdminLayout/index.ts +3 -0
  74. package/src/components/AdminLayout/types.ts +46 -0
  75. package/src/components/CouponCard/CouponCard.scss +55 -0
  76. package/src/components/CouponCard/CouponCard.tsx +687 -0
  77. package/src/components/CouponCard/index.ts +1 -0
  78. package/src/components/OrderForm/OrderForm.scss +148 -0
  79. package/src/components/OrderForm/OrderForm.tsx +503 -0
  80. package/src/components/OrderForm/index.ts +1 -0
  81. package/src/components/OrderList/OrderList.scss +160 -0
  82. package/src/components/OrderList/OrderList.tsx +885 -0
  83. package/src/components/OrderList/index.ts +1 -0
  84. package/src/components/ProductForm/ProductForm.scss +23 -0
  85. package/src/components/ProductForm/ProductForm.tsx +442 -0
  86. package/src/components/ProductForm/index.ts +3 -0
  87. package/src/components/ProductList/ProductList.scss +293 -0
  88. package/src/components/ProductList/ProductList.tsx +454 -0
  89. package/src/components/ProductList/index.ts +3 -0
  90. package/src/components/PromotionCard/PromotionCard.scss +71 -0
  91. package/src/components/PromotionCard/PromotionCard.tsx +579 -0
  92. package/src/components/PromotionCard/index.ts +1 -0
  93. package/src/components/RoleCard/RoleCard.scss +77 -0
  94. package/src/components/RoleCard/RoleCard.tsx +463 -0
  95. package/src/components/RoleCard/index.ts +1 -0
  96. package/src/components/UserCard/UserCard.scss +51 -0
  97. package/src/components/UserCard/UserCard.tsx +432 -0
  98. package/src/components/UserCard/index.ts +1 -0
  99. package/src/entry-components.ts +39 -0
  100. package/src/entry-meta.ts +23 -0
  101. package/src/index.scss +4 -0
  102. package/src/index.ts +36 -0
  103. package/src/index.tsx +17 -0
  104. package/src/meta/adminLayoutMeta.ts +154 -0
  105. package/src/meta/couponCardMeta.ts +287 -0
  106. package/src/meta/icons.ts +41 -0
  107. package/src/meta/orderFormMeta.ts +279 -0
  108. package/src/meta/orderListMeta.ts +443 -0
  109. package/src/meta/productFormMeta.ts +253 -0
  110. package/src/meta/productListMeta.ts +434 -0
  111. package/src/meta/promotionCardMeta.ts +276 -0
  112. package/src/meta/roleCardMeta.ts +142 -0
  113. package/src/meta/tabPaneMeta.ts +69 -0
  114. package/src/meta/userCardMeta.ts +128 -0
  115. package/src/meta.ts +25 -0
  116. package/src/setters/RestApiTester.tsx +219 -0
  117. package/src/shims/require.js +8 -0
  118. package/src/types/common.ts +19 -0
  119. package/src/types/marketing.ts +124 -0
  120. package/src/types/order.ts +169 -0
  121. package/src/types/permission.ts +102 -0
  122. package/src/types/product.ts +49 -0
@@ -0,0 +1,60 @@
1
+ import React from 'react'
2
+ import { Breadcrumb } from 'antd'
3
+ import type { BreadcrumbItem } from './types'
4
+
5
+ interface BreadcrumbProps {
6
+ items: BreadcrumbItem[]
7
+ onNavigate: (key: string) => void
8
+ }
9
+
10
+ const labelMap: Record<string, string> = {
11
+ dashboard: '工作台',
12
+ permission: '权限管理',
13
+ product: '商品管理',
14
+ order: '订单管理',
15
+ marketing: '营销管理',
16
+ users: '用户管理',
17
+ roles: '角色管理',
18
+ resources: '资源管理',
19
+ menus: '菜单管理',
20
+ list: '列表',
21
+ add: '添加',
22
+ category: '分类',
23
+ brand: '品牌',
24
+ attribute: '属性',
25
+ setting: '设置',
26
+ return: '退货',
27
+ reason: '原因',
28
+ coupon: '优惠券',
29
+ promotion: '促销',
30
+ flash: '秒杀',
31
+ new: '新品',
32
+ hot: '人气',
33
+ advertise: '广告',
34
+ subject: '专题',
35
+ }
36
+
37
+ const BreadcrumbNav: React.FC<BreadcrumbProps> = ({ items, onNavigate }) => {
38
+ const getLabel = (key: string): string => {
39
+ const parts = key.split('/')
40
+ return parts.map(part => labelMap[part] || part).join(' / ')
41
+ }
42
+
43
+ if (!items || items.length === 0) {
44
+ return null
45
+ }
46
+
47
+ return (
48
+ <Breadcrumb>
49
+ {items.map((item) => (
50
+ <Breadcrumb.Item key={item.key}>
51
+ <a onClick={() => onNavigate(item.key)}>
52
+ {item.label || getLabel(item.key)}
53
+ </a>
54
+ </Breadcrumb.Item>
55
+ ))}
56
+ </Breadcrumb>
57
+ )
58
+ }
59
+
60
+ export default BreadcrumbNav
@@ -0,0 +1,54 @@
1
+ import React, { ReactNode } from 'react'
2
+ import TabBar from './TabBar'
3
+ import type { TabItem } from './types'
4
+
5
+ interface MainContentProps {
6
+ children: ReactNode
7
+ pageKey: string
8
+ tabs: TabItem[]
9
+ activeTabKey: string
10
+ onTabClick: (key: string) => void
11
+ onCloseTab: (key: string) => void
12
+ onCloseOther: (keepKey: string) => void
13
+ onCloseAll: () => void
14
+ enableTabs?: boolean
15
+ closableTabs?: boolean
16
+ className?: string
17
+ }
18
+
19
+ const MainContent: React.FC<MainContentProps> = ({
20
+ children,
21
+ pageKey,
22
+ tabs,
23
+ activeTabKey,
24
+ onTabClick,
25
+ onCloseTab,
26
+ onCloseOther,
27
+ onCloseAll,
28
+ enableTabs = true,
29
+ closableTabs = true,
30
+ className = '',
31
+ }) => {
32
+ return (
33
+ <main className={`main-content ${className}`}>
34
+ {enableTabs && tabs.length > 0 && (
35
+ <TabBar
36
+ tabs={tabs}
37
+ activeTabKey={activeTabKey}
38
+ onTabClick={onTabClick}
39
+ onCloseTab={onCloseTab}
40
+ onCloseOther={onCloseOther}
41
+ onCloseAll={onCloseAll}
42
+ closableTabs={closableTabs}
43
+ />
44
+ )}
45
+ <div className="content-wrapper">
46
+ <div className="page-container" key={pageKey}>
47
+ {children}
48
+ </div>
49
+ </div>
50
+ </main>
51
+ )
52
+ }
53
+
54
+ export default MainContent
@@ -0,0 +1,76 @@
1
+ import React from 'react'
2
+ import { Dropdown, Avatar, Space, Menu } from 'antd'
3
+ import type { MenuProps } from 'antd'
4
+ import {
5
+ MenuFoldOutlined,
6
+ MenuUnfoldOutlined,
7
+ UserOutlined,
8
+ SettingOutlined,
9
+ LogoutOutlined,
10
+ } from '@ant-design/icons'
11
+ import BreadcrumbNav from './Breadcrumb'
12
+ import type { BreadcrumbItem } from './types'
13
+
14
+ interface NavbarProps {
15
+ collapsed: boolean
16
+ onToggleCollapse: () => void
17
+ breadcrumbItems: BreadcrumbItem[]
18
+ onNavigate: (key: string) => void
19
+ }
20
+
21
+ const Navbar: React.FC<NavbarProps> = ({ collapsed, onToggleCollapse, breadcrumbItems, onNavigate }) => {
22
+ const userMenuItems: MenuProps['items'] = [
23
+ {
24
+ key: 'profile',
25
+ icon: <UserOutlined />,
26
+ label: '个人中心',
27
+ },
28
+ {
29
+ key: 'settings',
30
+ icon: <SettingOutlined />,
31
+ label: '系统设置',
32
+ },
33
+ {
34
+ type: 'divider',
35
+ },
36
+ {
37
+ key: 'logout',
38
+ icon: <LogoutOutlined />,
39
+ label: '退出登录',
40
+ danger: true,
41
+ },
42
+ ]
43
+
44
+ const handleUserMenuClick: MenuProps['onClick'] = ({ key }) => {
45
+ if (key === 'logout') {
46
+ console.log('User logged out')
47
+ }
48
+ }
49
+
50
+ const menu = (
51
+ <Menu items={userMenuItems} onClick={handleUserMenuClick} />
52
+ )
53
+
54
+ return (
55
+ <div className="navbar-container">
56
+ <div className="navbar-left">
57
+ <button className="hamburger-btn" onClick={onToggleCollapse}>
58
+ {collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
59
+ </button>
60
+ <BreadcrumbNav items={breadcrumbItems} onNavigate={onNavigate} />
61
+ </div>
62
+ <div className="navbar-right">
63
+ <Dropdown overlay={menu} placement="bottomRight">
64
+ <div className="user-info">
65
+ <Avatar size={32} icon={<UserOutlined />} className="user-avatar" />
66
+ <Space size={4} className="username">
67
+ <span>管理员</span>
68
+ </Space>
69
+ </div>
70
+ </Dropdown>
71
+ </div>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ export default Navbar
@@ -0,0 +1,256 @@
1
+ import React, { useState } from 'react'
2
+ import { Menu } from 'antd'
3
+ import type { MenuProps } from 'antd'
4
+ import {
5
+ UserOutlined,
6
+ SafetyCertificateOutlined,
7
+ ShoppingOutlined,
8
+ FileTextOutlined,
9
+ TeamOutlined,
10
+ SettingOutlined,
11
+ TagsOutlined,
12
+ InboxOutlined,
13
+ DollarOutlined,
14
+ GiftOutlined,
15
+ ThunderboltOutlined,
16
+ FireOutlined,
17
+ DashboardOutlined,
18
+ } from '@ant-design/icons'
19
+ import type { MenuItem } from './types'
20
+
21
+ const { SubMenu } = Menu
22
+
23
+ interface SidebarProps {
24
+ collapsed: boolean
25
+ selectedKey: string
26
+ openKeys: string[]
27
+ onMenuClick: (key: string) => void
28
+ onOpenChange: (keys: string[]) => void
29
+ menuItems?: MenuItem[]
30
+ }
31
+
32
+ const defaultMenuItems: MenuItem[] = [
33
+ {
34
+ key: 'dashboard',
35
+ label: '工作台',
36
+ icon: <DashboardOutlined />,
37
+ path: '/dashboard',
38
+ },
39
+ {
40
+ key: 'permission',
41
+ label: '权限管理',
42
+ icon: <SafetyCertificateOutlined />,
43
+ children: [
44
+ {
45
+ key: 'permission/users',
46
+ label: '用户管理',
47
+ icon: <UserOutlined />,
48
+ path: '/permission/users',
49
+ },
50
+ {
51
+ key: 'permission/roles',
52
+ label: '角色管理',
53
+ icon: <TeamOutlined />,
54
+ path: '/permission/roles',
55
+ },
56
+ {
57
+ key: 'permission/resources',
58
+ label: '资源管理',
59
+ icon: <SettingOutlined />,
60
+ path: '/permission/resources',
61
+ },
62
+ {
63
+ key: 'permission/menus',
64
+ label: '菜单管理',
65
+ icon: <FileTextOutlined />,
66
+ path: '/permission/menus',
67
+ },
68
+ ],
69
+ },
70
+ {
71
+ key: 'product',
72
+ label: '商品管理',
73
+ icon: <ShoppingOutlined />,
74
+ children: [
75
+ {
76
+ key: 'product/list',
77
+ label: '商品列表',
78
+ icon: <InboxOutlined />,
79
+ path: '/product/list',
80
+ },
81
+ {
82
+ key: 'product/add',
83
+ label: '添加商品',
84
+ icon: <ShoppingOutlined />,
85
+ path: '/product/add',
86
+ },
87
+ {
88
+ key: 'product/category',
89
+ label: '商品分类',
90
+ icon: <TagsOutlined />,
91
+ path: '/product/category',
92
+ },
93
+ {
94
+ key: 'product/brand',
95
+ label: '品牌管理',
96
+ path: '/product/brand',
97
+ },
98
+ {
99
+ key: 'product/attribute',
100
+ label: '商品属性',
101
+ path: '/product/attribute',
102
+ },
103
+ ],
104
+ },
105
+ {
106
+ key: 'order',
107
+ label: '订单管理',
108
+ icon: <FileTextOutlined />,
109
+ children: [
110
+ {
111
+ key: 'order/list',
112
+ label: '订单列表',
113
+ icon: <FileTextOutlined />,
114
+ path: '/order/list',
115
+ },
116
+ {
117
+ key: 'order/setting',
118
+ label: '订单设置',
119
+ icon: <SettingOutlined />,
120
+ path: '/order/setting',
121
+ },
122
+ {
123
+ key: 'order/return',
124
+ label: '退货申请',
125
+ path: '/order/return',
126
+ },
127
+ {
128
+ key: 'order/reason',
129
+ label: '退货原因',
130
+ path: '/order/reason',
131
+ },
132
+ ],
133
+ },
134
+ {
135
+ key: 'marketing',
136
+ label: '营销管理',
137
+ icon: <GiftOutlined />,
138
+ children: [
139
+ {
140
+ key: 'marketing/coupon',
141
+ label: '优惠券管理',
142
+ icon: <GiftOutlined />,
143
+ path: '/marketing/coupon',
144
+ },
145
+ {
146
+ key: 'marketing/promotion',
147
+ label: '促销活动',
148
+ icon: <DollarOutlined />,
149
+ path: '/marketing/promotion',
150
+ },
151
+ {
152
+ key: 'marketing/flash',
153
+ label: '秒杀活动',
154
+ icon: <ThunderboltOutlined />,
155
+ path: '/marketing/flash',
156
+ },
157
+ {
158
+ key: 'marketing/new',
159
+ label: '新品推荐',
160
+ icon: <FireOutlined />,
161
+ path: '/marketing/new',
162
+ },
163
+ {
164
+ key: 'marketing/hot',
165
+ label: '人气推荐',
166
+ icon: <FireOutlined />,
167
+ path: '/marketing/hot',
168
+ },
169
+ {
170
+ key: 'marketing/advertise',
171
+ label: '广告管理',
172
+ path: '/marketing/advertise',
173
+ },
174
+ {
175
+ key: 'marketing/subject',
176
+ label: '专题管理',
177
+ path: '/marketing/subject',
178
+ },
179
+ {
180
+ key: 'marketing/brand',
181
+ label: '品牌推荐',
182
+ path: '/marketing/brand',
183
+ },
184
+ ],
185
+ },
186
+ ]
187
+
188
+ const Sidebar: React.FC<SidebarProps> = ({
189
+ collapsed,
190
+ selectedKey,
191
+ openKeys,
192
+ onMenuClick,
193
+ onOpenChange,
194
+ menuItems: customMenuItems,
195
+ }) => {
196
+ const items = customMenuItems || defaultMenuItems
197
+ const [internalOpenKeys, setInternalOpenKeys] = useState<string[]>(
198
+ items.filter(item => item.children).map(item => item.key)
199
+ )
200
+
201
+ const handleOpenChange: MenuProps['onOpenChange'] = (keys) => {
202
+ const latestKey = keys[keys.length - 1]
203
+ if (latestKey && items.some((item) => item.key === latestKey)) {
204
+ setInternalOpenKeys(keys)
205
+ onOpenChange(keys)
206
+ } else {
207
+ setInternalOpenKeys(latestKey ? [latestKey] : [])
208
+ onOpenChange(latestKey ? [latestKey] : [])
209
+ }
210
+ }
211
+
212
+ const handleClick: MenuProps['onClick'] = ({ key }) => {
213
+ onMenuClick(key)
214
+ }
215
+
216
+ return (
217
+ <div className={`sidebar-container ${collapsed ? 'collapsed' : ''}`}>
218
+ <div className="sidebar-logo">
219
+ {!collapsed && <span className="logo-text">Mall Admin</span>}
220
+ {collapsed && <span className="logo-icon">M</span>}
221
+ </div>
222
+ <Menu
223
+ mode="inline"
224
+ theme="dark"
225
+ inlineCollapsed={collapsed}
226
+ selectedKeys={[selectedKey]}
227
+ defaultOpenKeys={internalOpenKeys}
228
+ openKeys={openKeys.length > 0 ? openKeys : internalOpenKeys}
229
+ onClick={handleClick}
230
+ onOpenChange={handleOpenChange}
231
+ items={items.map((item) => {
232
+ if (item.children && item.children.length > 0) {
233
+ return {
234
+ key: item.key,
235
+ icon: item.icon,
236
+ label: item.label,
237
+ children: item.children.map((child) => ({
238
+ key: child.key,
239
+ icon: child.icon,
240
+ label: child.label,
241
+ })),
242
+ }
243
+ }
244
+ return {
245
+ key: item.key,
246
+ icon: item.icon,
247
+ label: item.label,
248
+ }
249
+ })}
250
+ />
251
+ </div>
252
+ )
253
+ }
254
+
255
+ export default Sidebar
256
+ export { defaultMenuItems }
@@ -0,0 +1,177 @@
1
+ import React, { useState, useRef, useEffect } from 'react'
2
+ import { Dropdown, Menu } from 'antd'
3
+ import type { MenuProps } from 'antd'
4
+ import { CloseOutlined, ReloadOutlined, CloseCircleOutlined } from '@ant-design/icons'
5
+ import type { TabItem } from './types'
6
+
7
+ interface TabBarProps {
8
+ tabs: TabItem[]
9
+ activeTabKey: string
10
+ onTabClick: (key: string) => void
11
+ onCloseTab: (key: string) => void
12
+ onCloseOther: (keepKey: string) => void
13
+ onCloseAll: () => void
14
+ closableTabs?: boolean
15
+ }
16
+
17
+ const TabBar: React.FC<TabBarProps> = ({
18
+ tabs,
19
+ activeTabKey,
20
+ onTabClick,
21
+ onCloseTab,
22
+ onCloseOther,
23
+ onCloseAll,
24
+ closableTabs = true,
25
+ }) => {
26
+ const [hoveredKey, setHoveredKey] = useState<string | null>(null)
27
+ const [contextMenuVisible, setContextMenuVisible] = useState(false)
28
+ const [contextMenuKey, setContextMenuKey] = useState<string>('')
29
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 })
30
+ const scrollContainerRef = useRef<HTMLDivElement>(null)
31
+
32
+ useEffect(() => {
33
+ if (scrollContainerRef.current && activeTabKey) {
34
+ const activeElement = scrollContainerRef.current.querySelector(`[data-tab-key="${activeTabKey}"]`)
35
+ if (activeElement) {
36
+ activeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
37
+ }
38
+ }
39
+ }, [activeTabKey])
40
+
41
+ useEffect(() => {
42
+ const handleClick = () => setContextMenuVisible(false)
43
+ if (contextMenuVisible) {
44
+ document.addEventListener('click', handleClick)
45
+ return () => document.removeEventListener('click', handleClick)
46
+ }
47
+ }, [contextMenuVisible])
48
+
49
+ const handleContextMenu = (e: React.MouseEvent, tabKey: string) => {
50
+ e.preventDefault()
51
+ e.stopPropagation()
52
+ setContextMenuKey(tabKey)
53
+ setMenuPosition({ x: e.clientX, y: e.clientY })
54
+ setContextMenuVisible(true)
55
+ }
56
+
57
+ const handleMenuClick = (key: string) => {
58
+ setContextMenuVisible(false)
59
+ switch (key) {
60
+ case 'close':
61
+ onCloseTab(contextMenuKey)
62
+ break
63
+ case 'closeOther':
64
+ onCloseOther(contextMenuKey)
65
+ break
66
+ case 'closeAll':
67
+ onCloseAll()
68
+ break
69
+ case 'refresh':
70
+ window.location.reload()
71
+ break
72
+ }
73
+ }
74
+
75
+ if (!tabs || tabs.length === 0) {
76
+ return null
77
+ }
78
+
79
+ const contextMenuItems: MenuProps['items'] = [
80
+ {
81
+ key: 'refresh',
82
+ icon: <ReloadOutlined />,
83
+ label: '刷新当前页',
84
+ },
85
+ {
86
+ key: 'close',
87
+ icon: <CloseOutlined />,
88
+ label: '关闭当前',
89
+ disabled: !closableTabs || tabs.find(t => t.key === contextMenuKey)?.closable === false,
90
+ },
91
+ {
92
+ type: 'divider',
93
+ },
94
+ {
95
+ key: 'closeOther',
96
+ icon: <CloseCircleOutlined />,
97
+ label: '关闭其他',
98
+ disabled: tabs.length <= 1,
99
+ },
100
+ {
101
+ key: 'closeAll',
102
+ icon: <CloseCircleOutlined />,
103
+ label: '关闭所有',
104
+ },
105
+ ]
106
+
107
+ const menu = (
108
+ <Menu items={contextMenuItems} onClick={({ key }) => handleMenuClick(key)} />
109
+ )
110
+
111
+ return (
112
+ <>
113
+ <div className="tab-bar-container">
114
+ <div className="tab-bar-scroll" ref={scrollContainerRef}>
115
+ {tabs.map((tab) => (
116
+ <div
117
+ key={tab.key}
118
+ data-tab-key={tab.key}
119
+ className={`tab-item ${activeTabKey === tab.key ? 'active' : ''} ${hoveredKey === tab.key ? 'hovered' : ''}`}
120
+ onClick={() => onTabClick(tab.key)}
121
+ onMouseEnter={() => setHoveredKey(tab.key)}
122
+ onMouseLeave={() => setHoveredKey(null)}
123
+ onContextMenu={(e) => handleContextMenu(e, tab.key)}
124
+ >
125
+ {tab.icon && <span className="tab-icon">{tab.icon}</span>}
126
+ <span className="tab-label">{tab.label}</span>
127
+ {closableTabs && tab.closable && (
128
+ <span
129
+ className={`tab-close-btn ${hoveredKey === tab.key || activeTabKey === tab.key ? 'visible' : ''}`}
130
+ onClick={(e) => {
131
+ e.stopPropagation()
132
+ onCloseTab(tab.key)
133
+ }}
134
+ >
135
+ <CloseOutlined style={{ fontSize: 10 }} />
136
+ </span>
137
+ )}
138
+ </div>
139
+ ))}
140
+ </div>
141
+ {closableTabs && tabs.length > 1 && (
142
+ <div className="tab-actions">
143
+ <button
144
+ className="close-all-btn"
145
+ onClick={onCloseAll}
146
+ title="关闭全部标签"
147
+ >
148
+ <CloseCircleOutlined />
149
+ </button>
150
+ </div>
151
+ )}
152
+ </div>
153
+
154
+ {contextMenuVisible && (
155
+ <div
156
+ style={{
157
+ position: 'fixed',
158
+ left: menuPosition.x,
159
+ top: menuPosition.y,
160
+ zIndex: 1000,
161
+ }}
162
+ >
163
+ <Dropdown
164
+ overlay={menu}
165
+ visible={contextMenuVisible}
166
+ onVisibleChange={setContextMenuVisible}
167
+ trigger={['click']}
168
+ >
169
+ <div style={{ width: 0, height: 0 }} />
170
+ </Dropdown>
171
+ </div>
172
+ )}
173
+ </>
174
+ )
175
+ }
176
+
177
+ export default TabBar
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+ import type { TabPaneProps } from './types'
3
+
4
+ const TabPane: React.FC<TabPaneProps> = ({
5
+ tab,
6
+ tabKey,
7
+ activeTabKey,
8
+ children,
9
+ className,
10
+ style,
11
+ }) => {
12
+ const isActive = activeTabKey === tabKey
13
+
14
+ return (
15
+ <div
16
+ className={`tab-pane ${isActive ? 'tab-pane--active' : ''} ${className || ''}`}
17
+ style={{
18
+ ...style,
19
+ display: isActive ? undefined : 'none',
20
+ }}
21
+ data-tab-key={tabKey}
22
+ data-active={isActive ? 'true' : 'false'}
23
+ >
24
+ {children}
25
+ </div>
26
+ )
27
+ }
28
+
29
+ export default TabPane
@@ -0,0 +1,3 @@
1
+ export { default as AdminLayout } from './AdminLayout'
2
+ export { default as TabPane } from './TabPane'
3
+ export type { AdminLayoutProps, TabPaneProps } from './types'
@@ -0,0 +1,46 @@
1
+ export interface MenuItem {
2
+ key: string
3
+ label: string
4
+ icon?: React.ReactNode
5
+ path?: string
6
+ children?: MenuItem[]
7
+ }
8
+
9
+ export interface BreadcrumbItem {
10
+ key: string
11
+ label: string
12
+ path?: string
13
+ }
14
+
15
+ export interface TabItem {
16
+ key: string
17
+ label: string
18
+ icon?: React.ReactNode
19
+ closable: boolean
20
+ path: string
21
+ }
22
+
23
+ export interface AdminLayoutProps {
24
+ defaultSelectedKey?: string
25
+ defaultOpenKeys?: string[]
26
+ collapsible?: boolean
27
+ enableTabs?: boolean
28
+ maxTabs?: number
29
+ closableTabs?: boolean
30
+ logoText?: string
31
+ menuItems?: MenuItem[]
32
+ style?: React.CSSProperties
33
+ className?: string
34
+ __designMode?: 'design' | 'preview'
35
+ componentId?: string
36
+ children?: React.ReactNode
37
+ }
38
+
39
+ export interface TabPaneProps {
40
+ tab?: string
41
+ tabKey?: string
42
+ activeTabKey?: string
43
+ children?: React.ReactNode
44
+ className?: string
45
+ style?: React.CSSProperties
46
+ }