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.
- package/README.md +128 -0
- package/build/_components-raw.css +791 -0
- package/build/_shims/antd.js +1 -0
- package/build/_shims/icons.js +1 -0
- package/build/_shims/moment.js +1 -0
- package/build/_shims/react-dom.js +1 -0
- package/build/_shims/react.js +1 -0
- package/build/adapters/DataSourceAdapter.d.ts +46 -0
- package/build/components/AdminLayout/AdminLayout.d.ts +5 -0
- package/build/components/AdminLayout/Breadcrumb.d.ts +8 -0
- package/build/components/AdminLayout/MainContent.d.ts +17 -0
- package/build/components/AdminLayout/Navbar.d.ts +10 -0
- package/build/components/AdminLayout/Sidebar.d.ts +14 -0
- package/build/components/AdminLayout/TabBar.d.ts +13 -0
- package/build/components/AdminLayout/TabPane.d.ts +4 -0
- package/build/components/AdminLayout/index.d.ts +3 -0
- package/build/components/AdminLayout/types.d.ts +42 -0
- package/build/components/CouponCard/CouponCard.d.ts +20 -0
- package/build/components/CouponCard/index.d.ts +1 -0
- package/build/components/OrderForm/OrderForm.d.ts +18 -0
- package/build/components/OrderForm/index.d.ts +1 -0
- package/build/components/OrderList/OrderList.d.ts +29 -0
- package/build/components/OrderList/index.d.ts +1 -0
- package/build/components/ProductForm/ProductForm.d.ts +18 -0
- package/build/components/ProductForm/index.d.ts +3 -0
- package/build/components/ProductList/ProductList.d.ts +47 -0
- package/build/components/ProductList/index.d.ts +3 -0
- package/build/components/PromotionCard/PromotionCard.d.ts +22 -0
- package/build/components/PromotionCard/index.d.ts +1 -0
- package/build/components/RoleCard/RoleCard.d.ts +18 -0
- package/build/components/RoleCard/index.d.ts +1 -0
- package/build/components/UserCard/UserCard.d.ts +17 -0
- package/build/components/UserCard/index.d.ts +1 -0
- package/build/entry-meta.d.ts +603 -0
- package/build/index.css +1 -0
- package/build/index.js +1 -0
- package/build/mall-components-meta.js +2563 -0
- package/build/mall-components.cdn.umd.css +1 -0
- package/build/mall-components.cdn.umd.js +8 -0
- package/build/mall-components.codesandbox.combined.js +1094 -0
- package/build/mall-components.codesandbox.css +401 -0
- package/build/mall-components.codesandbox.js +1080 -0
- package/build/mall-components.umd.css +1 -0
- package/build/mall-components.umd.js +8 -0
- package/build/meta/adminLayoutMeta.d.ts +3 -0
- package/build/meta/couponCardMeta.d.ts +128 -0
- package/build/meta/icons.d.ts +10 -0
- package/build/meta/orderFormMeta.d.ts +111 -0
- package/build/meta/orderListMeta.d.ts +170 -0
- package/build/meta/productFormMeta.d.ts +3 -0
- package/build/meta/productListMeta.d.ts +200 -0
- package/build/meta/promotionCardMeta.d.ts +129 -0
- package/build/meta/roleCardMeta.d.ts +3 -0
- package/build/meta/tabPaneMeta.d.ts +3 -0
- package/build/meta/userCardMeta.d.ts +3 -0
- package/build/meta.d.ts +605 -0
- package/build/setters/RestApiTester.d.ts +11 -0
- package/build/types/common.d.ts +17 -0
- package/build/types/marketing.d.ts +128 -0
- package/build/types/order.d.ts +174 -0
- package/build/types/permission.d.ts +101 -0
- package/build/types/product.d.ts +47 -0
- package/package.json +1 -0
- package/src/adapters/DataSourceAdapter.ts +445 -0
- package/src/components/AdminLayout/AdminLayout.scss +447 -0
- package/src/components/AdminLayout/AdminLayout.tsx +681 -0
- package/src/components/AdminLayout/Breadcrumb.tsx +60 -0
- package/src/components/AdminLayout/MainContent.tsx +54 -0
- package/src/components/AdminLayout/Navbar.tsx +76 -0
- package/src/components/AdminLayout/Sidebar.tsx +256 -0
- package/src/components/AdminLayout/TabBar.tsx +177 -0
- package/src/components/AdminLayout/TabPane.tsx +29 -0
- package/src/components/AdminLayout/index.ts +3 -0
- package/src/components/AdminLayout/types.ts +46 -0
- package/src/components/CouponCard/CouponCard.scss +55 -0
- package/src/components/CouponCard/CouponCard.tsx +687 -0
- package/src/components/CouponCard/index.ts +1 -0
- package/src/components/OrderForm/OrderForm.scss +148 -0
- package/src/components/OrderForm/OrderForm.tsx +503 -0
- package/src/components/OrderForm/index.ts +1 -0
- package/src/components/OrderList/OrderList.scss +160 -0
- package/src/components/OrderList/OrderList.tsx +885 -0
- package/src/components/OrderList/index.ts +1 -0
- package/src/components/ProductForm/ProductForm.scss +23 -0
- package/src/components/ProductForm/ProductForm.tsx +442 -0
- package/src/components/ProductForm/index.ts +3 -0
- package/src/components/ProductList/ProductList.scss +293 -0
- package/src/components/ProductList/ProductList.tsx +454 -0
- package/src/components/ProductList/index.ts +3 -0
- package/src/components/PromotionCard/PromotionCard.scss +71 -0
- package/src/components/PromotionCard/PromotionCard.tsx +579 -0
- package/src/components/PromotionCard/index.ts +1 -0
- package/src/components/RoleCard/RoleCard.scss +77 -0
- package/src/components/RoleCard/RoleCard.tsx +463 -0
- package/src/components/RoleCard/index.ts +1 -0
- package/src/components/UserCard/UserCard.scss +51 -0
- package/src/components/UserCard/UserCard.tsx +432 -0
- package/src/components/UserCard/index.ts +1 -0
- package/src/entry-components.ts +39 -0
- package/src/entry-meta.ts +23 -0
- package/src/index.scss +4 -0
- package/src/index.ts +36 -0
- package/src/index.tsx +17 -0
- package/src/meta/adminLayoutMeta.ts +154 -0
- package/src/meta/couponCardMeta.ts +287 -0
- package/src/meta/icons.ts +41 -0
- package/src/meta/orderFormMeta.ts +279 -0
- package/src/meta/orderListMeta.ts +443 -0
- package/src/meta/productFormMeta.ts +253 -0
- package/src/meta/productListMeta.ts +434 -0
- package/src/meta/promotionCardMeta.ts +276 -0
- package/src/meta/roleCardMeta.ts +142 -0
- package/src/meta/tabPaneMeta.ts +69 -0
- package/src/meta/userCardMeta.ts +128 -0
- package/src/meta.ts +25 -0
- package/src/setters/RestApiTester.tsx +219 -0
- package/src/shims/require.js +8 -0
- package/src/types/common.ts +19 -0
- package/src/types/marketing.ts +124 -0
- package/src/types/order.ts +169 -0
- package/src/types/permission.ts +102 -0
- package/src/types/product.ts +49 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import React, { useState, useCallback, useMemo, useEffect, useRef, Children, isValidElement } from 'react'
|
|
2
|
+
import Sidebar from './Sidebar'
|
|
3
|
+
import Navbar from './Navbar'
|
|
4
|
+
import MainContent from './MainContent'
|
|
5
|
+
import type { AdminLayoutProps, TabItem, BreadcrumbItem, MenuItem } from './types'
|
|
6
|
+
import { defaultMenuItems } from './Sidebar'
|
|
7
|
+
import './AdminLayout.scss'
|
|
8
|
+
|
|
9
|
+
const SCENARIO_NAME = 'general'
|
|
10
|
+
|
|
11
|
+
const saveSchemaToLocalStorage = () => {
|
|
12
|
+
try {
|
|
13
|
+
const engine = (window as any).AliLowCodeEngine
|
|
14
|
+
if (!engine?.project) return
|
|
15
|
+
|
|
16
|
+
const schema = engine.project.exportSchema('save')
|
|
17
|
+
const key = `${SCENARIO_NAME}:projectSchema`
|
|
18
|
+
localStorage.setItem(key, JSON.stringify(schema))
|
|
19
|
+
console.log('[AdminLayout] 💾 Schema saved to localStorage')
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error('[AdminLayout] Failed to save schema:', e)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const findMenuItem = (items: MenuItem[], key: string): MenuItem | undefined => {
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
if (item.key === key) return item
|
|
28
|
+
if (item.children) {
|
|
29
|
+
const found = findMenuItem(item.children, key)
|
|
30
|
+
if (found) return found
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const getLabelFromKey = (key: string): string => {
|
|
37
|
+
const parts = key.split('/')
|
|
38
|
+
const labelMap: Record<string, string> = {
|
|
39
|
+
dashboard: '工作台',
|
|
40
|
+
permission: '权限管理',
|
|
41
|
+
product: '商品管理',
|
|
42
|
+
order: '订单管理',
|
|
43
|
+
marketing: '营销管理',
|
|
44
|
+
users: '用户管理',
|
|
45
|
+
roles: '角色管理',
|
|
46
|
+
resources: '资源管理',
|
|
47
|
+
menus: '菜单管理',
|
|
48
|
+
list: '列表',
|
|
49
|
+
add: '添加',
|
|
50
|
+
category: '分类',
|
|
51
|
+
brand: '品牌',
|
|
52
|
+
attribute: '属性',
|
|
53
|
+
setting: '设置',
|
|
54
|
+
return: '退货申请',
|
|
55
|
+
reason: '退货原因',
|
|
56
|
+
coupon: '优惠券管理',
|
|
57
|
+
promotion: '促销活动',
|
|
58
|
+
flash: '秒杀活动',
|
|
59
|
+
new: '新品推荐',
|
|
60
|
+
hot: '人气推荐',
|
|
61
|
+
advertise: '广告管理',
|
|
62
|
+
subject: '专题管理',
|
|
63
|
+
}
|
|
64
|
+
return labelMap[parts[parts.length - 1]] || parts[parts.length - 1]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const AdminLayout: React.FC<AdminLayoutProps> = ({
|
|
68
|
+
defaultSelectedKey = 'dashboard',
|
|
69
|
+
defaultOpenKeys,
|
|
70
|
+
collapsible = true,
|
|
71
|
+
enableTabs = true,
|
|
72
|
+
maxTabs = 10,
|
|
73
|
+
closableTabs = true,
|
|
74
|
+
logoText = 'Mall Admin',
|
|
75
|
+
menuItems: customMenuItems,
|
|
76
|
+
style,
|
|
77
|
+
className,
|
|
78
|
+
children,
|
|
79
|
+
__designMode,
|
|
80
|
+
componentId,
|
|
81
|
+
}) => {
|
|
82
|
+
const items = customMenuItems || defaultMenuItems
|
|
83
|
+
|
|
84
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
85
|
+
const [selectedKey, setSelectedKey] = useState(defaultSelectedKey)
|
|
86
|
+
const [openKeys, setOpenKeys] = useState<string[]>(defaultOpenKeys || [])
|
|
87
|
+
|
|
88
|
+
const [tabs, setTabs] = useState<TabItem[]>(() => {
|
|
89
|
+
const initialItem = findMenuItem(items, defaultSelectedKey)
|
|
90
|
+
return [{
|
|
91
|
+
key: defaultSelectedKey,
|
|
92
|
+
label: initialItem?.label || getLabelFromKey(defaultSelectedKey),
|
|
93
|
+
icon: initialItem?.icon,
|
|
94
|
+
closable: false,
|
|
95
|
+
path: initialItem?.path || `/${defaultSelectedKey}`,
|
|
96
|
+
}]
|
|
97
|
+
})
|
|
98
|
+
const [activeTabKey, setActiveTabKey] = useState(defaultSelectedKey)
|
|
99
|
+
const activeTabKeyRef = useRef(activeTabKey)
|
|
100
|
+
activeTabKeyRef.current = activeTabKey
|
|
101
|
+
const initializedRef = useRef(false)
|
|
102
|
+
|
|
103
|
+
// 初始化:从 schema 恢复 tabs state,并为工作台创建默认 TabPane
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (__designMode !== 'design' || !componentId || initializedRef.current) return
|
|
106
|
+
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
try {
|
|
109
|
+
const engine = (window as any).AliLowCodeEngine
|
|
110
|
+
if (!engine?.project?.currentDocument) return
|
|
111
|
+
|
|
112
|
+
const documentModel = engine.project.currentDocument
|
|
113
|
+
const adminLayoutNode = documentModel.getNodeById(componentId)
|
|
114
|
+
|
|
115
|
+
if (!adminLayoutNode) {
|
|
116
|
+
console.log('[AdminLayout] ⚠️ AdminLayout node not found')
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 使用 schema.children 获取子节点(因为 children.toArray() 可能返回空)
|
|
121
|
+
const schemaChildren = adminLayoutNode.schema?.children || []
|
|
122
|
+
console.log('[AdminLayout] 🔍 Schema children:', schemaChildren.length, schemaChildren.map((c: any) => c.componentName))
|
|
123
|
+
|
|
124
|
+
// 获取所有 TabPane 子节点
|
|
125
|
+
const existingTabPanes = schemaChildren.filter((child: any) => child.componentName === 'TabPane')
|
|
126
|
+
|
|
127
|
+
console.log('[AdminLayout] 🔍 Found existing TabPanes:', existingTabPanes.length)
|
|
128
|
+
|
|
129
|
+
// 从 schema 恢复 tabs state
|
|
130
|
+
if (existingTabPanes.length > 0) {
|
|
131
|
+
const restoredTabs: TabItem[] = existingTabPanes.map((tpSchema: any) => {
|
|
132
|
+
const tabKey = tpSchema.props?.tabKey || 'unknown'
|
|
133
|
+
const tabLabel = tpSchema.props?.tab || getLabelFromKey(tabKey)
|
|
134
|
+
const menuItem = findMenuItem(items, tabKey)
|
|
135
|
+
|
|
136
|
+
console.log('[AdminLayout] 📋 Restoring tab:', tabKey, tabLabel)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
key: tabKey,
|
|
140
|
+
label: tabLabel,
|
|
141
|
+
icon: menuItem?.icon,
|
|
142
|
+
closable: tabKey !== 'dashboard',
|
|
143
|
+
path: menuItem?.path || `/${tabKey}`,
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
console.log('[AdminLayout] 📋 Restored tabs from schema:', restoredTabs.map(t => t.key))
|
|
148
|
+
setTabs(restoredTabs)
|
|
149
|
+
|
|
150
|
+
// 设置当前激活的 tab(使用最后一个)
|
|
151
|
+
const currentActiveTab = restoredTabs[restoredTabs.length - 1]
|
|
152
|
+
if (currentActiveTab) {
|
|
153
|
+
setActiveTabKey(currentActiveTab.key)
|
|
154
|
+
setSelectedKey(currentActiveTab.key)
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// 没有 TabPane,为工作台创建默认 TabPane
|
|
158
|
+
console.log('[AdminLayout] 📋 No TabPane found, creating default dashboard TabPane')
|
|
159
|
+
const tabPaneNode = documentModel.createNode({
|
|
160
|
+
componentName: 'TabPane',
|
|
161
|
+
props: {
|
|
162
|
+
tab: '工作台',
|
|
163
|
+
tabKey: 'dashboard',
|
|
164
|
+
activeTabKey: 'dashboard',
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (tabPaneNode) {
|
|
169
|
+
documentModel.insertNode(adminLayoutNode, tabPaneNode)
|
|
170
|
+
console.log('[AdminLayout] ✅ Created default TabPane for dashboard')
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
initializedRef.current = true
|
|
175
|
+
} catch (e) {
|
|
176
|
+
console.error('[AdminLayout] Init TabPane error:', e)
|
|
177
|
+
}
|
|
178
|
+
}, 100)
|
|
179
|
+
|
|
180
|
+
return () => clearTimeout(timer)
|
|
181
|
+
}, [componentId, __designMode, items])
|
|
182
|
+
|
|
183
|
+
// 监听 schema 变化,自动保存(防抖)
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (__designMode !== 'design') return
|
|
186
|
+
|
|
187
|
+
const engine = (window as any).AliLowCodeEngine
|
|
188
|
+
if (!engine?.project?.currentDocument) return
|
|
189
|
+
|
|
190
|
+
const documentModel = engine.project.currentDocument
|
|
191
|
+
|
|
192
|
+
let saveTimer: ReturnType<typeof setTimeout> | null = null
|
|
193
|
+
|
|
194
|
+
const debouncedSave = () => {
|
|
195
|
+
if (saveTimer) clearTimeout(saveTimer)
|
|
196
|
+
saveTimer = setTimeout(() => {
|
|
197
|
+
saveSchemaToLocalStorage()
|
|
198
|
+
}, 500)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 监听节点添加
|
|
202
|
+
const offAddNode = documentModel.onAddNode?.(() => {
|
|
203
|
+
console.log('[AdminLayout] 📌 onAddNode triggered')
|
|
204
|
+
debouncedSave()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// 监听节点删除
|
|
208
|
+
const offRemoveNode = documentModel.onRemoveNode?.(() => {
|
|
209
|
+
console.log('[AdminLayout] 📌 onRemoveNode triggered')
|
|
210
|
+
debouncedSave()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// 监听节点挂载(拖拽完成)
|
|
214
|
+
const offMountNode = documentModel.onMountNode?.(() => {
|
|
215
|
+
console.log('[AdminLayout] 📌 onMountNode triggered')
|
|
216
|
+
debouncedSave()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return () => {
|
|
220
|
+
if (saveTimer) clearTimeout(saveTimer)
|
|
221
|
+
offAddNode?.()
|
|
222
|
+
offRemoveNode?.()
|
|
223
|
+
offMountNode?.()
|
|
224
|
+
}
|
|
225
|
+
}, [__designMode])
|
|
226
|
+
|
|
227
|
+
// 同步 activeTabKey 到所有 TabPane
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (__designMode !== 'design' || !componentId) return
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const engine = (window as any).AliLowCodeEngine
|
|
233
|
+
if (!engine?.project?.currentDocument) return
|
|
234
|
+
|
|
235
|
+
const documentModel = engine.project.currentDocument
|
|
236
|
+
const adminLayoutNode = documentModel.getNodeById(componentId)
|
|
237
|
+
|
|
238
|
+
if (adminLayoutNode?.children) {
|
|
239
|
+
const childrenArr = Array.isArray(adminLayoutNode.children)
|
|
240
|
+
? adminLayoutNode.children
|
|
241
|
+
: (adminLayoutNode.children.toArray ? adminLayoutNode.children.toArray() : [])
|
|
242
|
+
|
|
243
|
+
childrenArr.forEach((child: any) => {
|
|
244
|
+
if (child.componentName === 'TabPane') {
|
|
245
|
+
child.setPropValue('activeTabKey', activeTabKey)
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
// silent fail
|
|
251
|
+
}
|
|
252
|
+
}, [activeTabKey, componentId, __designMode])
|
|
253
|
+
|
|
254
|
+
const handleToggleCollapse = useCallback(() => {
|
|
255
|
+
if (collapsible) setCollapsed(prev => !prev)
|
|
256
|
+
}, [collapsible])
|
|
257
|
+
|
|
258
|
+
const createTabPane = useCallback((tabKey: string, tabLabel: string) => {
|
|
259
|
+
if (typeof window === 'undefined') return false
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const engine = (window as any).AliLowCodeEngine
|
|
263
|
+
if (!engine?.project?.currentDocument) return false
|
|
264
|
+
|
|
265
|
+
const documentModel = engine.project.currentDocument
|
|
266
|
+
const adminLayoutNode = componentId ? documentModel.getNodeById(componentId) : null
|
|
267
|
+
if (!adminLayoutNode) return false
|
|
268
|
+
|
|
269
|
+
// 收集所有现有的 TabPane 节点信息用于调试
|
|
270
|
+
const allChildren = Array.isArray(adminLayoutNode.children)
|
|
271
|
+
? adminLayoutNode.children
|
|
272
|
+
: (adminLayoutNode.children?.toArray ? adminLayoutNode.children.toArray() : [])
|
|
273
|
+
|
|
274
|
+
console.log('[AdminLayout] 🔍 All children before create:',
|
|
275
|
+
allChildren.map((c: any) => ({ id: c.id, name: c.componentName, tabKey: c.props?.tabKey })))
|
|
276
|
+
|
|
277
|
+
// 强制查找并删除已存在的同 key TabPane(防止残留)
|
|
278
|
+
for (const child of allChildren) {
|
|
279
|
+
const ck = child.props?.tabKey || child.getPropValue?.('tabKey')
|
|
280
|
+
if (child.componentName === 'TabPane' && ck === tabKey) {
|
|
281
|
+
console.log('[AdminLayout] ⚠️ Found existing TabPane, removing first:', child.id)
|
|
282
|
+
|
|
283
|
+
// 先清空子节点
|
|
284
|
+
if (child.children) {
|
|
285
|
+
const grandChildren = Array.isArray(child.children)
|
|
286
|
+
? [...child.children]
|
|
287
|
+
: (child.children.toArray ? [...child.children.toArray()] : [])
|
|
288
|
+
|
|
289
|
+
console.log('[AdminLayout] 🗑️ Removing', grandChildren.length, 'children from existing TabPane')
|
|
290
|
+
for (const gc of grandChildren) {
|
|
291
|
+
try { documentModel.removeNode(gc) } catch(e) {}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 删除旧 TabPane
|
|
296
|
+
documentModel.removeNode(child)
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 创建新 TabPane
|
|
302
|
+
const tabPaneNode = documentModel.createNode({
|
|
303
|
+
componentName: 'TabPane',
|
|
304
|
+
props: {
|
|
305
|
+
tab: tabLabel,
|
|
306
|
+
tabKey: tabKey,
|
|
307
|
+
activeTabKey: activeTabKeyRef.current,
|
|
308
|
+
},
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
if (!tabPaneNode) return false
|
|
312
|
+
|
|
313
|
+
documentModel.insertNode(adminLayoutNode, tabPaneNode)
|
|
314
|
+
console.log('[AdminLayout] ✅ Created new TabPane:', tabKey, 'nodeId:', tabPaneNode.id)
|
|
315
|
+
|
|
316
|
+
// 保存 schema 到 localStorage
|
|
317
|
+
setTimeout(() => saveSchemaToLocalStorage(), 100)
|
|
318
|
+
|
|
319
|
+
return true
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('[AdminLayout] createTabPane error:', error)
|
|
322
|
+
return false
|
|
323
|
+
}
|
|
324
|
+
}, [componentId])
|
|
325
|
+
|
|
326
|
+
const removeTabPane = useCallback((tabKey: string): boolean => {
|
|
327
|
+
if (typeof window === 'undefined') return false
|
|
328
|
+
|
|
329
|
+
let removed = false
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const engine = (window as any).AliLowCodeEngine
|
|
333
|
+
if (!engine?.project?.currentDocument) return false
|
|
334
|
+
|
|
335
|
+
const documentModel = engine.project.currentDocument
|
|
336
|
+
const adminLayoutNode = componentId ? documentModel.getNodeById(componentId) : null
|
|
337
|
+
|
|
338
|
+
console.log('[AdminLayout] 🗑️ removeTabPane called:', tabKey)
|
|
339
|
+
console.log('[AdminLayout] componentId:', componentId)
|
|
340
|
+
console.log('[AdminLayout] adminLayoutNode:', adminLayoutNode ? 'found' : 'NOT FOUND')
|
|
341
|
+
console.log('[AdminLayout] adminLayoutNode.id:', adminLayoutNode?.id)
|
|
342
|
+
console.log('[AdminLayout] adminLayoutNode.componentName:', adminLayoutNode?.componentName)
|
|
343
|
+
|
|
344
|
+
if (!adminLayoutNode) return false
|
|
345
|
+
|
|
346
|
+
// 尝试多种方式获取 children
|
|
347
|
+
let childrenArr: any[] = []
|
|
348
|
+
|
|
349
|
+
// 方式1: 直接访问 children
|
|
350
|
+
if (adminLayoutNode.children) {
|
|
351
|
+
if (Array.isArray(adminLayoutNode.children)) {
|
|
352
|
+
childrenArr = adminLayoutNode.children
|
|
353
|
+
} else if (typeof adminLayoutNode.children.toArray === 'function') {
|
|
354
|
+
childrenArr = adminLayoutNode.children.toArray()
|
|
355
|
+
} else if (typeof adminLayoutNode.children.map === 'function') {
|
|
356
|
+
childrenArr = adminLayoutNode.children.map((c: any) => c)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// 方式2: 通过 schema 获取 children
|
|
361
|
+
if (childrenArr.length === 0 && adminLayoutNode.schema?.children) {
|
|
362
|
+
console.log('[AdminLayout] Trying schema.children...')
|
|
363
|
+
const schemaChildren = adminLayoutNode.schema.children
|
|
364
|
+
if (Array.isArray(schemaChildren)) {
|
|
365
|
+
childrenArr = schemaChildren.map((childSchema: any) => {
|
|
366
|
+
return documentModel.getNodeById(childSchema.id)
|
|
367
|
+
}).filter(Boolean)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 方式3: 通过 documentModel 获取
|
|
372
|
+
if (childrenArr.length === 0) {
|
|
373
|
+
console.log('[AdminLayout] Trying documentModel.getRoot()...')
|
|
374
|
+
const root = documentModel.getRoot()
|
|
375
|
+
console.log('[AdminLayout] Root node:', root?.componentName)
|
|
376
|
+
|
|
377
|
+
// 递归查找 AdminLayout 节点
|
|
378
|
+
const findNode = (node: any, targetId: string | undefined): any => {
|
|
379
|
+
if (!targetId) return null
|
|
380
|
+
if (node.id === targetId) return node
|
|
381
|
+
if (node.children) {
|
|
382
|
+
const arr = Array.isArray(node.children) ? node.children :
|
|
383
|
+
(node.children.toArray ? node.children.toArray() : [])
|
|
384
|
+
for (const child of arr) {
|
|
385
|
+
const found = findNode(child, targetId)
|
|
386
|
+
if (found) return found
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return null
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const foundNode = findNode(root, componentId)
|
|
393
|
+
if (foundNode && foundNode.children) {
|
|
394
|
+
childrenArr = Array.isArray(foundNode.children) ? foundNode.children :
|
|
395
|
+
(foundNode.children.toArray ? foundNode.children.toArray() : [])
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log('[AdminLayout] Final children count:', childrenArr.length)
|
|
400
|
+
console.log('[AdminLayout] Children details:',
|
|
401
|
+
childrenArr.map((c: any) => ({
|
|
402
|
+
id: c?.id,
|
|
403
|
+
name: c?.componentName,
|
|
404
|
+
tabKey: c?.props?.tabKey || c?.getPropValue?.('tabKey'),
|
|
405
|
+
childrenCount: c?.children ?
|
|
406
|
+
(Array.isArray(c.children) ? c.children.length :
|
|
407
|
+
(c.children.toArray ? c.children.toArray().length : 0)) : 0
|
|
408
|
+
})))
|
|
409
|
+
|
|
410
|
+
// 打印每个子节点的详细信息
|
|
411
|
+
childrenArr.forEach((c: any, idx: number) => {
|
|
412
|
+
console.log(`[AdminLayout] Child[${idx}] ${c?.componentName}:`, {
|
|
413
|
+
id: c?.id,
|
|
414
|
+
tabKey: c?.props?.tabKey || c?.getPropValue?.('tabKey'),
|
|
415
|
+
hasChildren: !!c?.children,
|
|
416
|
+
childrenType: c?.children ?
|
|
417
|
+
(Array.isArray(c.children) ? 'array' :
|
|
418
|
+
(typeof c.children.toArray === 'function' ? 'array-like' : typeof c.children)) : 'none'
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
// 如果是 TabPane,打印其子节点
|
|
422
|
+
if (c?.componentName === 'TabPane' && c?.children) {
|
|
423
|
+
const tpChildren = Array.isArray(c.children) ? c.children :
|
|
424
|
+
(c.children.toArray ? c.children.toArray() : [])
|
|
425
|
+
console.log(`[AdminLayout] TabPane children:`,
|
|
426
|
+
tpChildren.map((gc: any) => ({ id: gc?.id, name: gc?.componentName })))
|
|
427
|
+
|
|
428
|
+
// 尝试其他方式获取 children
|
|
429
|
+
if (tpChildren.length === 0) {
|
|
430
|
+
console.log(`[AdminLayout] Trying alternative methods...`)
|
|
431
|
+
|
|
432
|
+
// 方法1: schema.children
|
|
433
|
+
if (c.schema?.children) {
|
|
434
|
+
console.log(`[AdminLayout] schema.children:`, c.schema.children)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 方法2: getChildren
|
|
438
|
+
if (typeof c.getChildren === 'function') {
|
|
439
|
+
const gChildren = c.getChildren()
|
|
440
|
+
console.log(`[AdminLayout] getChildren():`, gChildren)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 方法3: slots
|
|
444
|
+
if (c.slots) {
|
|
445
|
+
console.log(`[AdminLayout] slots:`, c.slots)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 如果不是 TabPane,说明是拖入的组件
|
|
451
|
+
if (c?.componentName !== 'TabPane') {
|
|
452
|
+
console.log(`[AdminLayout] ⚠️ Found non-TabPane child! This might be a dragged component.`)
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
for (const child of childrenArr) {
|
|
457
|
+
if (!child) continue
|
|
458
|
+
|
|
459
|
+
const childTabKey = child.props?.tabKey || child.getPropValue?.('tabKey')
|
|
460
|
+
|
|
461
|
+
if (child.componentName === 'TabPane' && childTabKey === tabKey) {
|
|
462
|
+
console.log('[AdminLayout] ✅ Found TabPane to remove:', child.id)
|
|
463
|
+
|
|
464
|
+
// 第一步:获取并删除所有子节点
|
|
465
|
+
// 使用 schema.children 来获取子节点(因为 children.toArray() 可能返回空)
|
|
466
|
+
const schemaChildren = child.schema?.children
|
|
467
|
+
if (schemaChildren && Array.isArray(schemaChildren) && schemaChildren.length > 0) {
|
|
468
|
+
console.log('[AdminLayout] Removing', schemaChildren.length, 'grandchildren from schema.children')
|
|
469
|
+
|
|
470
|
+
// 倒序删除避免索引问题
|
|
471
|
+
for (let i = schemaChildren.length - 1; i >= 0; i--) {
|
|
472
|
+
const childSchema = schemaChildren[i]
|
|
473
|
+
const grandChildNode = documentModel.getNodeById(childSchema.id)
|
|
474
|
+
if (grandChildNode) {
|
|
475
|
+
try {
|
|
476
|
+
documentModel.removeNode(grandChildNode)
|
|
477
|
+
console.log('[AdminLayout] ✓ Removed grandchild:', grandChildNode.id, grandChildNode.componentName)
|
|
478
|
+
} catch (e) {
|
|
479
|
+
console.warn('[AdminLayout] ✗ Failed to remove grandchild:', e)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
console.log('[AdminLayout] No grandchildren to remove')
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// 第二步:删除 TabPane 本身
|
|
488
|
+
try {
|
|
489
|
+
documentModel.removeNode(child)
|
|
490
|
+
removed = true
|
|
491
|
+
console.log('[AdminLayout] ✅ Removed TabPane node:', tabKey)
|
|
492
|
+
|
|
493
|
+
// 保存 schema 到 localStorage
|
|
494
|
+
setTimeout(() => saveSchemaToLocalStorage(), 100)
|
|
495
|
+
} catch (e) {
|
|
496
|
+
console.error('[AdminLayout] Failed to remove TabPane:', e)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
break
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!removed) {
|
|
504
|
+
console.warn('[AdminLayout] ⚠️ TabPane not found for key:', tabKey)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return removed
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error('[AdminLayout] removeTabPane error:', error)
|
|
510
|
+
return false
|
|
511
|
+
}
|
|
512
|
+
}, [componentId])
|
|
513
|
+
|
|
514
|
+
const handleMenuClick = useCallback((key: string) => {
|
|
515
|
+
setSelectedKey(key)
|
|
516
|
+
|
|
517
|
+
const existingTab = tabs.find(tab => tab.key === key)
|
|
518
|
+
if (!existingTab) {
|
|
519
|
+
const menuItem = findMenuItem(items, key)
|
|
520
|
+
const newTab: TabItem = {
|
|
521
|
+
key,
|
|
522
|
+
label: menuItem?.label || getLabelFromKey(key),
|
|
523
|
+
icon: menuItem?.icon,
|
|
524
|
+
closable: key !== 'dashboard',
|
|
525
|
+
path: menuItem?.path || `/${key}`,
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
setTabs(prev => {
|
|
529
|
+
if (prev.length >= maxTabs) {
|
|
530
|
+
const filtered = prev.filter(t => t.closable)
|
|
531
|
+
if (filtered.length > 0) {
|
|
532
|
+
return [...prev.filter(t => t.key !== filtered[0].key), newTab]
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return [...prev, newTab]
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
if (__designMode === 'design') {
|
|
539
|
+
setTimeout(() => createTabPane(key, menuItem?.label || getLabelFromKey(key)), 0)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
setActiveTabKey(key)
|
|
544
|
+
}, [tabs, items, maxTabs, __designMode, createTabPane])
|
|
545
|
+
|
|
546
|
+
const handleOpenChange = useCallback((keys: string[]) => setOpenKeys(keys), [])
|
|
547
|
+
const handleNavigate = useCallback((key: string) => {
|
|
548
|
+
setSelectedKey(key)
|
|
549
|
+
setActiveTabKey(key)
|
|
550
|
+
}, [])
|
|
551
|
+
const handleTabClick = useCallback((key: string) => {
|
|
552
|
+
setSelectedKey(key)
|
|
553
|
+
setActiveTabKey(key)
|
|
554
|
+
}, [])
|
|
555
|
+
|
|
556
|
+
const handleCloseTab = useCallback((key: string) => {
|
|
557
|
+
if (key === 'dashboard') return
|
|
558
|
+
|
|
559
|
+
// 先删除 TabPane 节点
|
|
560
|
+
removeTabPane(key)
|
|
561
|
+
|
|
562
|
+
setTabs(prev => prev.filter(tab => tab.key !== key))
|
|
563
|
+
|
|
564
|
+
if (key === activeTabKey) {
|
|
565
|
+
const remainingTabs = tabs.filter(tab => tab.key !== key)
|
|
566
|
+
if (remainingTabs.length > 0) {
|
|
567
|
+
const currentIndex = tabs.findIndex(tab => tab.key === key)
|
|
568
|
+
const nextTab = remainingTabs[currentIndex] || remainingTabs[remainingTabs.length - 1]
|
|
569
|
+
setSelectedKey(nextTab.key)
|
|
570
|
+
setActiveTabKey(nextTab.key)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}, [tabs, activeTabKey, removeTabPane])
|
|
574
|
+
|
|
575
|
+
const handleCloseOther = useCallback((keepKey: string) => {
|
|
576
|
+
tabs.forEach(tab => {
|
|
577
|
+
if (tab.key !== keepKey && tab.key !== 'dashboard') {
|
|
578
|
+
removeTabPane(tab.key)
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
setTabs(prev => prev.filter(tab => tab.key === keepKey || tab.key === 'dashboard'))
|
|
583
|
+
setSelectedKey(keepKey)
|
|
584
|
+
setActiveTabKey(keepKey)
|
|
585
|
+
}, [tabs, removeTabPane])
|
|
586
|
+
|
|
587
|
+
const handleCloseAll = useCallback(() => {
|
|
588
|
+
tabs.forEach(tab => {
|
|
589
|
+
if (tab.key !== 'dashboard') {
|
|
590
|
+
removeTabPane(tab.key)
|
|
591
|
+
}
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
const dashboardTab = tabs.find(tab => tab.key === 'dashboard')
|
|
595
|
+
if (dashboardTab) {
|
|
596
|
+
setTabs([dashboardTab])
|
|
597
|
+
setSelectedKey('dashboard')
|
|
598
|
+
setActiveTabKey('dashboard')
|
|
599
|
+
}
|
|
600
|
+
}, [tabs, removeTabPane])
|
|
601
|
+
|
|
602
|
+
const breadcrumbItems = useMemo((): BreadcrumbItem[] => {
|
|
603
|
+
if (selectedKey === 'dashboard') return []
|
|
604
|
+
const parts = selectedKey.split('/')
|
|
605
|
+
if (parts.length === 1) return [{ key: selectedKey, label: '', path: selectedKey }]
|
|
606
|
+
return [{ key: selectedKey, label: '', path: selectedKey }]
|
|
607
|
+
}, [selectedKey])
|
|
608
|
+
|
|
609
|
+
// 条件渲染:只渲染 activeTabKey 对应的 TabPane
|
|
610
|
+
const filteredChildren = useMemo(() => {
|
|
611
|
+
if (!children) return null
|
|
612
|
+
|
|
613
|
+
// 在设计模式下,过滤 children 只渲染当前 activeTabKey 的 TabPane
|
|
614
|
+
if (__designMode === 'design') {
|
|
615
|
+
const childArray = Children.toArray(children)
|
|
616
|
+
const activeTabPane = childArray.find((child) => {
|
|
617
|
+
if (isValidElement(child)) {
|
|
618
|
+
const childProps = child.props as any
|
|
619
|
+
return childProps?.tabKey === activeTabKey
|
|
620
|
+
}
|
|
621
|
+
return false
|
|
622
|
+
})
|
|
623
|
+
return activeTabPane || null
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// 预览模式下,渲染所有 children(TabPane 内部会处理显隐)
|
|
627
|
+
return children
|
|
628
|
+
}, [children, activeTabKey, __designMode])
|
|
629
|
+
|
|
630
|
+
const defaultContent = (
|
|
631
|
+
<div style={{ padding: 24, color: '#999' }}>
|
|
632
|
+
点击侧边栏菜单创建选项卡,然后拖拽组件到对应面板
|
|
633
|
+
</div>
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
const content = filteredChildren || defaultContent
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<div className={`admin-layout ${collapsed ? 'is-collapse' : ''} ${className || ''}`} style={style} data-active-tab={activeTabKey}>
|
|
640
|
+
<Sidebar
|
|
641
|
+
collapsed={collapsed}
|
|
642
|
+
selectedKey={selectedKey}
|
|
643
|
+
openKeys={openKeys}
|
|
644
|
+
onMenuClick={handleMenuClick}
|
|
645
|
+
onOpenChange={handleOpenChange}
|
|
646
|
+
menuItems={items}
|
|
647
|
+
/>
|
|
648
|
+
<div className="layout-right">
|
|
649
|
+
<Navbar
|
|
650
|
+
collapsed={collapsed}
|
|
651
|
+
onToggleCollapse={handleToggleCollapse}
|
|
652
|
+
breadcrumbItems={breadcrumbItems}
|
|
653
|
+
onNavigate={handleNavigate}
|
|
654
|
+
/>
|
|
655
|
+
<MainContent
|
|
656
|
+
pageKey={selectedKey}
|
|
657
|
+
tabs={tabs}
|
|
658
|
+
activeTabKey={activeTabKey}
|
|
659
|
+
onTabClick={handleTabClick}
|
|
660
|
+
onCloseTab={handleCloseTab}
|
|
661
|
+
onCloseOther={handleCloseOther}
|
|
662
|
+
onCloseAll={handleCloseAll}
|
|
663
|
+
enableTabs={enableTabs}
|
|
664
|
+
closableTabs={closableTabs}
|
|
665
|
+
className={collapsed ? 'collapsed' : ''}
|
|
666
|
+
>
|
|
667
|
+
{content}
|
|
668
|
+
</MainContent>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
{collapsible && (
|
|
672
|
+
<div
|
|
673
|
+
className={`mobile-overlay ${!collapsed ? 'visible' : ''}`}
|
|
674
|
+
onClick={handleToggleCollapse}
|
|
675
|
+
/>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export default AdminLayout
|