Skip to content

Commit 3f3a3b2

Browse files
committed
feat:新增标签页右键快捷菜单功能(fac>=0.2.10rc18) #I838PN
1 parent 93f8982 commit 3f3a3b2

3 files changed

Lines changed: 298 additions & 36 deletions

File tree

dash-fastapi-frontend/callbacks/layout_c/head_c.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def logout_confirm(okCounts):
7272
return true;
7373
}
7474
''',
75-
Output('trigger-reload-output', 'reload'),
75+
Output('trigger-reload-output', 'reload', allow_duplicate=True),
7676
Input('index-reload', 'nClicks'),
7777
prevent_initial_call=True
7878
)

dash-fastapi-frontend/callbacks/layout_c/index_c.py

Lines changed: 279 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import dash
22
from dash.dependencies import Input, Output, State
3+
from dash.exceptions import PreventUpdate
34
import feffery_antd_components as fac
45
from jsonpath_ng import parse
56
from flask import json
@@ -11,8 +12,8 @@
1112

1213

1314
@app.callback(
14-
[Output('tabs-container', 'items'),
15-
Output('tabs-container', 'activeKey')],
15+
[Output('tabs-container', 'items', allow_duplicate=True),
16+
Output('tabs-container', 'activeKey', allow_duplicate=True)],
1617
[Input('index-side-menu', 'currentKey'),
1718
Input('tabs-container', 'latestDeletePane')],
1819
[State('tabs-container', 'items'),
@@ -37,6 +38,7 @@ def handle_tab_switch_and_create(currentKey, latestDeletePane, origin_items, act
3738
# 先前已更新为非None的按钮的nClicks误触发通知弹出回调
3839
parser = parse('$..nClicks')
3940
origin_items = parser.update(origin_items, None)
41+
new_items = dash.Patch()
4042

4143
if trigger_id == 'index-side-menu':
4244

@@ -57,27 +59,58 @@ def handle_tab_switch_and_create(currentKey, latestDeletePane, origin_items, act
5759
# 判断当前选中的菜单栏项是否存在module,如果有,则动态导入module,否则返回404页面
5860
menu_modules = find_modules_by_key(menu_info.get('menu_info'), currentKey)
5961

62+
for index, item in enumerate(origin_items):
63+
if {'key': '关闭右侧', 'label': '关闭右侧', 'icon': 'antd-arrow-right'} not in item['contextMenu']:
64+
item['contextMenu'].insert(-1, {
65+
'key': '关闭右侧',
66+
'label': '关闭右侧',
67+
'icon': 'antd-arrow-right'
68+
})
69+
new_items[index]['contextMenu'] = item['contextMenu']
70+
context_menu = [
71+
{
72+
'key': '刷新页面',
73+
'label': '刷新页面',
74+
'icon': 'antd-reload'
75+
},
76+
{
77+
'key': '关闭当前',
78+
'label': '关闭当前',
79+
'icon': 'antd-close'
80+
},
81+
{
82+
'key': '关闭其他',
83+
'label': '关闭其他',
84+
'icon': 'antd-close-circle'
85+
},
86+
{
87+
'key': '全部关闭',
88+
'label': '全部关闭',
89+
'icon': 'antd-close-circle'
90+
}
91+
]
92+
if len(origin_items) != 1:
93+
context_menu.insert(-1, {
94+
'key': '关闭左侧',
95+
'label': '关闭左侧',
96+
'icon': 'antd-arrow-left'
97+
})
6098
if menu_modules:
6199
if menu_modules == 'link':
62-
return [dash.no_update] * 2
100+
raise PreventUpdate
63101
else:
64102
# 否则追加子项返回
65103
# 其中若各标签页内元素类似,则推荐配合模式匹配构建交互逻辑
66-
return [
67-
[
68-
*origin_items,
69-
{
70-
'label': menu_title,
71-
'key': currentKey,
72-
'children': eval('views.' + menu_modules + '.render(button_perms)'),
73-
}
74-
],
75-
currentKey
76-
]
77-
78-
return [
79-
[
80-
*origin_items,
104+
new_items.append(
105+
{
106+
'label': menu_title,
107+
'key': currentKey,
108+
'children': eval('views.' + menu_modules + '.render(button_perms)'),
109+
'contextMenu': context_menu
110+
}
111+
)
112+
else:
113+
new_items.append(
81114
{
82115
'label': menu_title,
83116
'key': currentKey,
@@ -90,34 +123,246 @@ def handle_tab_switch_and_create(currentKey, latestDeletePane, origin_items, act
90123
'paddingTop': 0
91124
}
92125
),
126+
'contextMenu': context_menu
93127
}
94-
],
128+
)
129+
130+
return [
131+
new_items,
95132
currentKey
96133
]
97134

98135
elif trigger_id == 'tabs-container':
99136

100-
# 若要删除的是当前正激活的标签页
101-
if latestDeletePane == activeKey:
137+
# 如果删除的是当前标签页则回到最后新增的标签页,否则保持当前标签页不变
138+
for index, item in enumerate(origin_items):
139+
if item['key'] == latestDeletePane:
140+
context_menu = [
141+
{
142+
'key': '刷新页面',
143+
'label': '刷新页面',
144+
'icon': 'antd-reload'
145+
},
146+
{
147+
'key': '关闭其他',
148+
'label': '关闭其他',
149+
'icon': 'antd-close-circle'
150+
},
151+
{
152+
'key': '全部关闭',
153+
'label': '全部关闭',
154+
'icon': 'antd-close-circle'
155+
}
156+
]
157+
if index == 1 and len(origin_items) == 2:
158+
new_items[0]['contextMenu'] = context_menu
159+
elif len(origin_items) == 3:
160+
context_menu.insert(1, {
161+
'key': '关闭当前',
162+
'label': '关闭当前',
163+
'icon': 'antd-close'
164+
})
165+
if index == 1:
166+
new_items[2]['contextMenu'] = context_menu
167+
if index == 2:
168+
new_items[1]['contextMenu'] = context_menu
169+
else:
170+
if index == len(origin_items) - 1:
171+
new_items[index - 1]['contextMenu'] = item['contextMenu']
172+
new_items.remove(item)
173+
break
174+
new_origin_items = [
175+
item for item in
176+
origin_items if item['key'] != latestDeletePane
177+
]
178+
179+
return [
180+
new_items,
181+
new_origin_items[-1]['key'] if activeKey == latestDeletePane else activeKey
182+
]
183+
184+
raise PreventUpdate
185+
186+
187+
@app.callback(
188+
[Output('tabs-container', 'items', allow_duplicate=True),
189+
Output('tabs-container', 'activeKey', allow_duplicate=True),
190+
Output('trigger-reload-output', 'reload', allow_duplicate=True)],
191+
Input('tabs-container', 'clickedContextMenu'),
192+
[State('tabs-container', 'items'),
193+
State('tabs-container', 'activeKey')],
194+
prevent_initial_call=True
195+
)
196+
def handle_via_context_menu(clickedContextMenu, origin_items, activeKey):
197+
"""
198+
基于标签页标题右键菜单的额外标签页控制
199+
"""
200+
if clickedContextMenu['menuKey'] == '刷新页面':
201+
202+
return [
203+
dash.no_update,
204+
dash.no_update,
205+
True
206+
]
207+
208+
if '关闭' in clickedContextMenu['menuKey']:
209+
new_items = dash.Patch()
210+
if clickedContextMenu['menuKey'] == '关闭当前':
211+
for index, item in enumerate(origin_items):
212+
if item['key'] == clickedContextMenu['tabKey']:
213+
context_menu = [
214+
{
215+
'key': '刷新页面',
216+
'label': '刷新页面',
217+
'icon': 'antd-reload'
218+
},
219+
{
220+
'key': '关闭其他',
221+
'label': '关闭其他',
222+
'icon': 'antd-close-circle'
223+
},
224+
{
225+
'key': '全部关闭',
226+
'label': '全部关闭',
227+
'icon': 'antd-close-circle'
228+
}
229+
]
230+
if index == 1 and len(origin_items) == 2:
231+
new_items[0]['contextMenu'] = context_menu
232+
elif len(origin_items) == 3:
233+
context_menu.insert(1, {
234+
'key': '关闭当前',
235+
'label': '关闭当前',
236+
'icon': 'antd-close'
237+
})
238+
if index == 1:
239+
new_items[2]['contextMenu'] = context_menu
240+
if index == 2:
241+
new_items[1]['contextMenu'] = context_menu
242+
else:
243+
if index == len(origin_items) - 1:
244+
new_items[index - 1]['contextMenu'] = item['contextMenu']
245+
new_items.remove(item)
246+
break
247+
new_origin_items = [
248+
item for item in
249+
origin_items if item['key'] != clickedContextMenu['tabKey']
250+
]
251+
102252
return [
103-
[
104-
item
105-
for item in origin_items
106-
if item['key'] != latestDeletePane
107-
],
108-
'首页'
253+
new_items,
254+
new_origin_items[-1]['key'] if activeKey == clickedContextMenu['tabKey'] else activeKey,
255+
dash.no_update
109256
]
110257

111-
# 否则保持当前激活的标签页子项不变,删去目标子项
258+
elif clickedContextMenu['menuKey'] == '关闭其他':
259+
for item in origin_items:
260+
if item['key'] != clickedContextMenu['tabKey'] and item['key'] != '首页':
261+
new_items.remove(item)
262+
context_menu = [
263+
{
264+
'key': '刷新页面',
265+
'label': '刷新页面',
266+
'icon': 'antd-reload'
267+
},
268+
{
269+
'key': '关闭其他',
270+
'label': '关闭其他',
271+
'icon': 'antd-close-circle'
272+
},
273+
{
274+
'key': '全部关闭',
275+
'label': '全部关闭',
276+
'icon': 'antd-close-circle'
277+
}
278+
]
279+
if clickedContextMenu['tabKey'] == '首页':
280+
new_items[0]['contextMenu'] = context_menu
281+
else:
282+
context_menu.insert(1, {
283+
'key': '关闭当前',
284+
'label': '关闭当前',
285+
'icon': 'antd-close'
286+
})
287+
new_items[1]['contextMenu'] = context_menu
288+
289+
return [
290+
new_items,
291+
clickedContextMenu['tabKey'],
292+
dash.no_update
293+
]
294+
295+
elif clickedContextMenu['menuKey'] == '关闭左侧':
296+
current_index = 0
297+
for index, item in enumerate(origin_items):
298+
if item['key'] == clickedContextMenu['tabKey']:
299+
current_index = index
300+
item['contextMenu'].remove({
301+
'key': '关闭左侧',
302+
'label': '关闭左侧',
303+
'icon': 'antd-arrow-left'
304+
})
305+
new_items[index]['contextMenu'] = item['contextMenu']
306+
break
307+
for item in origin_items[1:current_index]:
308+
new_items.remove(item)
309+
310+
return [
311+
new_items,
312+
clickedContextMenu['tabKey'],
313+
dash.no_update
314+
]
315+
316+
elif clickedContextMenu['menuKey'] == '关闭右侧':
317+
current_index = 0
318+
for index, item in enumerate(origin_items):
319+
if item['key'] == clickedContextMenu['tabKey']:
320+
current_index = index
321+
item['contextMenu'].remove({
322+
'key': '关闭右侧',
323+
'label': '关闭右侧',
324+
'icon': 'antd-arrow-right'
325+
})
326+
new_items[index]['contextMenu'] = item['contextMenu']
327+
break
328+
for item in origin_items[current_index+1:]:
329+
new_items.remove(item)
330+
331+
return [
332+
new_items,
333+
clickedContextMenu['tabKey'],
334+
dash.no_update
335+
]
336+
337+
for item in origin_items:
338+
if item['key'] != '首页':
339+
new_items.remove(item)
340+
new_items[0]['contextMenu'] = [
341+
{
342+
'key': '刷新页面',
343+
'label': '刷新页面',
344+
'icon': 'antd-reload'
345+
},
346+
{
347+
'key': '关闭其他',
348+
'label': '关闭其他',
349+
'icon': 'antd-close-circle'
350+
},
351+
{
352+
'key': '全部关闭',
353+
'label': '全部关闭',
354+
'icon': 'antd-close-circle'
355+
}
356+
]
357+
# 否则则为全部关闭
112358
return [
113-
[
114-
item
115-
for item in origin_items
116-
if item['key'] != latestDeletePane
117-
],
359+
new_items,
360+
'首页',
118361
dash.no_update
119362
]
120363

364+
raise PreventUpdate
365+
121366

122367
# 页首面包屑和hash回调
123368
@app.callback(
@@ -175,4 +420,4 @@ def get_current_breadcrumbs(active_key, menu_info):
175420
current_href
176421
]
177422

178-
return dash.no_update
423+
raise PreventUpdate

0 commit comments

Comments
 (0)