You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
16 KiB
442 lines
16 KiB
import dash
|
|
from dash.dependencies import Input, Output, State
|
|
from dash.exceptions import PreventUpdate
|
|
import feffery_antd_components as fac
|
|
from jsonpath_ng import parse
|
|
from flask import json, session
|
|
from collections import OrderedDict
|
|
|
|
from server import app
|
|
import views
|
|
from utils.tree_tool import find_title_by_key, find_modules_by_key, find_href_by_key, find_parents
|
|
|
|
|
|
@app.callback(
|
|
[Output('tabs-container', 'items', allow_duplicate=True),
|
|
Output('tabs-container', 'activeKey', allow_duplicate=True)],
|
|
[Input('index-side-menu', 'currentKey'),
|
|
Input('tabs-container', 'tabCloseCounts')],
|
|
[State('tabs-container', 'latestDeletePane'),
|
|
State('tabs-container', 'items'),
|
|
State('tabs-container', 'activeKey'),
|
|
State('menu-info-store-container', 'data'),
|
|
State('menu-list-store-container', 'data')],
|
|
prevent_initial_call=True
|
|
)
|
|
def handle_tab_switch_and_create(currentKey, tabCloseCounts, latestDeletePane, origin_items, activeKey, menu_info, menu_list):
|
|
"""
|
|
这个回调函数用于处理标签页子项的新建、切换及删除
|
|
具体策略:
|
|
1.当左侧某个菜单项被新选中,且右侧标签页子项尚未包含此项时,新建并切换
|
|
2.当左侧某个菜单项被新选中,且右侧标签页子项已包含此项时,切换
|
|
3.当右侧标签页子项某项被删除时,销毁对应标签页的同时切换回最后新增的标签页
|
|
"""
|
|
|
|
trigger_id = dash.ctx.triggered_id
|
|
|
|
# 基于jsonpath对各标签页子项中所有已有记录的nClicks参数重置为None
|
|
# 以避免每次新的items返回给标签页重新渲染后,
|
|
# 先前已更新为非None的按钮的nClicks误触发通知弹出回调
|
|
parser = parse('$..nClicks')
|
|
origin_items = parser.update(origin_items, None)
|
|
new_items = dash.Patch()
|
|
|
|
if trigger_id == 'index-side-menu':
|
|
|
|
# 判断当前新选中的菜单栏项对应标签页是否已创建
|
|
if currentKey in [item['key'] for item in origin_items]:
|
|
return [
|
|
dash.no_update,
|
|
currentKey
|
|
]
|
|
|
|
if currentKey == '个人资料':
|
|
menu_title = '个人资料'
|
|
button_perms = []
|
|
role_perms = []
|
|
menu_modules = 'system.user.profile'
|
|
else:
|
|
menu_title = find_title_by_key(menu_info.get('menu_info'), currentKey)
|
|
button_perms = [item.get('perms') for item in menu_list.get('menu_list') if str(item.get('parent_id')) == currentKey]
|
|
role_perms = [item.get('role_key') for item in session.get('role_info')]
|
|
# 判断当前选中的菜单栏项是否存在module,如果有,则动态导入module,否则返回404页面
|
|
menu_modules = find_modules_by_key(menu_info.get('menu_info'), currentKey)
|
|
|
|
for index, item in enumerate(origin_items):
|
|
if {'key': '关闭右侧', 'label': '关闭右侧', 'icon': 'antd-arrow-right'} not in item['contextMenu']:
|
|
item['contextMenu'].insert(-1, {
|
|
'key': '关闭右侧',
|
|
'label': '关闭右侧',
|
|
'icon': 'antd-arrow-right'
|
|
})
|
|
new_items[index]['contextMenu'] = item['contextMenu']
|
|
context_menu = [
|
|
{
|
|
'key': '刷新页面',
|
|
'label': '刷新页面',
|
|
'icon': 'antd-reload'
|
|
},
|
|
{
|
|
'key': '关闭当前',
|
|
'label': '关闭当前',
|
|
'icon': 'antd-close'
|
|
},
|
|
{
|
|
'key': '关闭其他',
|
|
'label': '关闭其他',
|
|
'icon': 'antd-close-circle'
|
|
},
|
|
{
|
|
'key': '全部关闭',
|
|
'label': '全部关闭',
|
|
'icon': 'antd-close-circle'
|
|
}
|
|
]
|
|
if len(origin_items) != 1:
|
|
context_menu.insert(-1, {
|
|
'key': '关闭左侧',
|
|
'label': '关闭左侧',
|
|
'icon': 'antd-arrow-left'
|
|
})
|
|
if menu_modules:
|
|
if menu_modules == 'link':
|
|
raise PreventUpdate
|
|
else:
|
|
# 否则追加子项返回
|
|
# 其中若各标签页内元素类似,则推荐配合模式匹配构建交互逻辑
|
|
new_items.append(
|
|
{
|
|
'label': menu_title,
|
|
'key': currentKey,
|
|
'children': eval('views.' + menu_modules + '.render(button_perms=button_perms, role_perms=role_perms)'),
|
|
'contextMenu': context_menu
|
|
}
|
|
)
|
|
else:
|
|
new_items.append(
|
|
{
|
|
'label': menu_title,
|
|
'key': currentKey,
|
|
'children': fac.AntdResult(
|
|
status='404',
|
|
title='页面不存在',
|
|
subTitle='请先配置该路由的页面',
|
|
style={
|
|
'paddingBottom': 0,
|
|
'paddingTop': 0
|
|
}
|
|
),
|
|
'contextMenu': context_menu
|
|
}
|
|
)
|
|
|
|
return [
|
|
new_items,
|
|
currentKey
|
|
]
|
|
|
|
elif trigger_id == 'tabs-container':
|
|
|
|
# 如果删除的是当前标签页则回到最后新增的标签页,否则保持当前标签页不变
|
|
for index, item in enumerate(origin_items):
|
|
if item['key'] == latestDeletePane:
|
|
context_menu = [
|
|
{
|
|
'key': '刷新页面',
|
|
'label': '刷新页面',
|
|
'icon': 'antd-reload'
|
|
},
|
|
{
|
|
'key': '关闭其他',
|
|
'label': '关闭其他',
|
|
'icon': 'antd-close-circle'
|
|
},
|
|
{
|
|
'key': '全部关闭',
|
|
'label': '全部关闭',
|
|
'icon': 'antd-close-circle'
|
|
}
|
|
]
|
|
if index == 1:
|
|
if len(origin_items) == 2:
|
|
new_items[0]['contextMenu'] = context_menu
|
|
else:
|
|
origin_items[2]['contextMenu'].remove({
|
|
'key': '关闭左侧',
|
|
'label': '关闭左侧',
|
|
'icon': 'antd-arrow-left'
|
|
})
|
|
new_items[2]['contextMenu'] = origin_items[2]['contextMenu']
|
|
elif index == 2:
|
|
if len(origin_items) == 3:
|
|
origin_items[1]['contextMenu'].remove({
|
|
'key': '关闭右侧',
|
|
'label': '关闭右侧',
|
|
'icon': 'antd-arrow-right'
|
|
})
|
|
new_items[1]['contextMenu'] = origin_items[1]['contextMenu']
|
|
else:
|
|
if index == len(origin_items) - 1:
|
|
new_items[index - 1]['contextMenu'] = item['contextMenu']
|
|
del new_items[index]
|
|
break
|
|
new_origin_items = [
|
|
item for item in
|
|
origin_items if item['key'] != latestDeletePane
|
|
]
|
|
|
|
return [
|
|
new_items,
|
|
new_origin_items[-1]['key'] if activeKey == latestDeletePane else activeKey
|
|
]
|
|
|
|
raise PreventUpdate
|
|
|
|
|
|
@app.callback(
|
|
[Output('tabs-container', 'items', allow_duplicate=True),
|
|
Output('tabs-container', 'activeKey', allow_duplicate=True),
|
|
Output('trigger-reload-output', 'reload', allow_duplicate=True)],
|
|
Input('tabs-container', 'clickedContextMenu'),
|
|
[State('tabs-container', 'items'),
|
|
State('tabs-container', 'activeKey')],
|
|
prevent_initial_call=True
|
|
)
|
|
def handle_via_context_menu(clickedContextMenu, origin_items, activeKey):
|
|
"""
|
|
基于标签页标题右键菜单的额外标签页控制
|
|
"""
|
|
if clickedContextMenu['menuKey'] == '刷新页面':
|
|
|
|
return [
|
|
dash.no_update,
|
|
dash.no_update,
|
|
True
|
|
]
|
|
|
|
if '关闭' in clickedContextMenu['menuKey']:
|
|
new_items = dash.Patch()
|
|
if clickedContextMenu['menuKey'] == '关闭当前':
|
|
for index, item in enumerate(origin_items):
|
|
if item['key'] == clickedContextMenu['tabKey']:
|
|
context_menu = [
|
|
{
|
|
'key': '刷新页面',
|
|
'label': '刷新页面',
|
|
'icon': 'antd-reload'
|
|
},
|
|
{
|
|
'key': '关闭其他',
|
|
'label': '关闭其他',
|
|
'icon': 'antd-close-circle'
|
|
},
|
|
{
|
|
'key': '全部关闭',
|
|
'label': '全部关闭',
|
|
'icon': 'antd-close-circle'
|
|
}
|
|
]
|
|
if index == 1:
|
|
if len(origin_items) == 2:
|
|
new_items[0]['contextMenu'] = context_menu
|
|
else:
|
|
origin_items[2]['contextMenu'].remove({
|
|
'key': '关闭左侧',
|
|
'label': '关闭左侧',
|
|
'icon': 'antd-arrow-left'
|
|
})
|
|
new_items[2]['contextMenu'] = origin_items[2]['contextMenu']
|
|
elif index == 2:
|
|
if len(origin_items) == 3:
|
|
origin_items[1]['contextMenu'].remove({
|
|
'key': '关闭右侧',
|
|
'label': '关闭右侧',
|
|
'icon': 'antd-arrow-right'
|
|
})
|
|
new_items[1]['contextMenu'] = origin_items[1]['contextMenu']
|
|
else:
|
|
if index == len(origin_items) - 1:
|
|
new_items[index - 1]['contextMenu'] = item['contextMenu']
|
|
del new_items[index]
|
|
break
|
|
new_origin_items = [
|
|
item for item in
|
|
origin_items if item['key'] != clickedContextMenu['tabKey']
|
|
]
|
|
|
|
return [
|
|
new_items,
|
|
new_origin_items[-1]['key'] if activeKey == clickedContextMenu['tabKey'] else activeKey,
|
|
dash.no_update
|
|
]
|
|
|
|
elif clickedContextMenu['menuKey'] == '关闭其他':
|
|
current_index = 0
|
|
for index, item in enumerate(origin_items):
|
|
if item['key'] == clickedContextMenu['tabKey']:
|
|
current_index = index
|
|
for i in range(1, current_index):
|
|
del new_items[1]
|
|
for j in range(current_index+1, len(origin_items)+1):
|
|
del new_items[2]
|
|
context_menu = [
|
|
{
|
|
'key': '刷新页面',
|
|
'label': '刷新页面',
|
|
'icon': 'antd-reload'
|
|
},
|
|
{
|
|
'key': '关闭其他',
|
|
'label': '关闭其他',
|
|
'icon': 'antd-close-circle'
|
|
},
|
|
{
|
|
'key': '全部关闭',
|
|
'label': '全部关闭',
|
|
'icon': 'antd-close-circle'
|
|
}
|
|
]
|
|
if clickedContextMenu['tabKey'] == '首页':
|
|
new_items[0]['contextMenu'] = context_menu
|
|
else:
|
|
context_menu.insert(1, {
|
|
'key': '关闭当前',
|
|
'label': '关闭当前',
|
|
'icon': 'antd-close'
|
|
})
|
|
new_items[1]['contextMenu'] = context_menu
|
|
|
|
return [
|
|
new_items,
|
|
clickedContextMenu['tabKey'],
|
|
dash.no_update
|
|
]
|
|
|
|
elif clickedContextMenu['menuKey'] == '关闭左侧':
|
|
current_index = 0
|
|
for index, item in enumerate(origin_items):
|
|
if item['key'] == clickedContextMenu['tabKey']:
|
|
current_index = index
|
|
item['contextMenu'].remove({
|
|
'key': '关闭左侧',
|
|
'label': '关闭左侧',
|
|
'icon': 'antd-arrow-left'
|
|
})
|
|
new_items[index]['contextMenu'] = item['contextMenu']
|
|
break
|
|
for i in range(1, current_index):
|
|
del new_items[1]
|
|
|
|
return [
|
|
new_items,
|
|
clickedContextMenu['tabKey'],
|
|
dash.no_update
|
|
]
|
|
|
|
elif clickedContextMenu['menuKey'] == '关闭右侧':
|
|
current_index = 0
|
|
for index, item in enumerate(origin_items):
|
|
if item['key'] == clickedContextMenu['tabKey']:
|
|
current_index = index
|
|
item['contextMenu'].remove({
|
|
'key': '关闭右侧',
|
|
'label': '关闭右侧',
|
|
'icon': 'antd-arrow-right'
|
|
})
|
|
new_items[index]['contextMenu'] = item['contextMenu']
|
|
break
|
|
for i in range(current_index+1, len(origin_items)+1):
|
|
del new_items[current_index+1]
|
|
|
|
return [
|
|
new_items,
|
|
clickedContextMenu['tabKey'],
|
|
dash.no_update
|
|
]
|
|
|
|
for i in range(len(origin_items)):
|
|
del new_items[1]
|
|
new_items[0]['contextMenu'] = [
|
|
{
|
|
'key': '刷新页面',
|
|
'label': '刷新页面',
|
|
'icon': 'antd-reload'
|
|
},
|
|
{
|
|
'key': '关闭其他',
|
|
'label': '关闭其他',
|
|
'icon': 'antd-close-circle'
|
|
},
|
|
{
|
|
'key': '全部关闭',
|
|
'label': '全部关闭',
|
|
'icon': 'antd-close-circle'
|
|
}
|
|
]
|
|
# 否则则为全部关闭
|
|
return [
|
|
new_items,
|
|
'首页',
|
|
dash.no_update
|
|
]
|
|
|
|
raise PreventUpdate
|
|
|
|
|
|
# 页首面包屑和hash回调
|
|
@app.callback(
|
|
[Output('header-breadcrumb', 'items'),
|
|
Output('dcc-url', 'pathname', allow_duplicate=True)],
|
|
Input('tabs-container', 'activeKey'),
|
|
State('menu-info-store-container', 'data'),
|
|
prevent_initial_call=True
|
|
)
|
|
def get_current_breadcrumbs(active_key, menu_info):
|
|
if active_key:
|
|
|
|
if active_key == '首页':
|
|
return [
|
|
[
|
|
{
|
|
'title': '首页',
|
|
'icon': 'antd-dashboard',
|
|
'href': '/'
|
|
},
|
|
],
|
|
'/'
|
|
]
|
|
|
|
elif active_key == '个人资料':
|
|
return [
|
|
[
|
|
{
|
|
'title': '首页',
|
|
'icon': 'antd-dashboard',
|
|
'href': '/'
|
|
},
|
|
{
|
|
'title': '个人资料',
|
|
}
|
|
],
|
|
'/user/profile'
|
|
]
|
|
|
|
else:
|
|
result = find_parents(menu_info.get('menu_info'), active_key)
|
|
# 去除result的重复项
|
|
parent_info = list(OrderedDict((json.dumps(d, ensure_ascii=False), d) for d in result).values())
|
|
if parent_info:
|
|
current_href = find_href_by_key(menu_info.get('menu_info'), active_key)
|
|
|
|
return [
|
|
[
|
|
{
|
|
'title': '首页',
|
|
'icon': 'antd-dashboard',
|
|
'href': '/'
|
|
},
|
|
] + parent_info,
|
|
current_href
|
|
]
|
|
|
|
raise PreventUpdate
|
|
|