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,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