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

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