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.
 
 

510 lines
19 KiB

import uuid
import dash
import time
from dash import html, dcc
from dash.dependencies import Input, Output, State, ClientsideFunction
import feffery_antd_components as fac
import feffery_utils_components as fuc
from flask import session
from operator import itemgetter
from server import app
from config.env import AppConfig
from config.global_config import RouterConfig
from store.store import render_store_container
# 载入子页面
import views
from callbacks import app_c
from api.login import get_current_user_info_api
from utils.tree_tool import find_node_values, find_key_by_href, deal_user_menu_info, get_search_panel_data
from views.aichat.components import aichat
app.layout = html.Div(
[
# 注入url监听
fuc.FefferyLocation(id='url-container'),
# 用于回调pathname信息
dcc.Location(id='dcc-url', refresh=False),
# 注入js执行容器
fuc.FefferyExecuteJs(
id='execute-js-container'
),
# 注入页面内容挂载点
html.Div(id='app-mount'),
# 注入全局配置容器
fac.AntdConfigProvider(id='app-config-provider'),
# 注入快捷搜索面板
fuc.FefferyShortcutPanel(
id='search-panel',
data=[],
placeholder='输入你想要搜索的菜单...',
panelStyles={
'accentColor': '#1890ff',
'zIndex': 99999
}
),
# 聊天记录的id
dcc.Store("chat_session_id"),
# 聊天窗口显示按钮
fac.AntdButton(
id='ai_chat_button',
type='link',
children=html.Img(src="/assets/imgs/aichat.gif",
style={"width": "100%", "height": "100%"}),
style={
'z-index': '90000',
'position': 'fixed',
'bottom': '30px',
'right': '0px',
'display': 'none',
'width': '64px',
'height': '64px',
'padding': '0px'
}
),
html.Div(
[aichat.render(),
html.Div(
children=[
fac.AntdButton(
id="aichat_zoom-close",
children=html.Img(src="/assets/imgs/collapse-diagonal-line.png"),
type='link',
style={
"font-size": "20px",
"padding": '5px',
"display": "none",
"margin-top": "-15px"
}
),
fac.AntdButton(
id="aichat_zoom-open",
children=html.Img(src="/assets/imgs/expand-diagonal-fill.png"),
type='link',
style={
"font-size": "20px",
"padding": '5px',
"display": "block",
"margin-top": "-15px"
}
),
fac.AntdButton(
id="close-chat-window",
children=(
fac.AntdIcon(
style={"color": "#909399"},
icon="antd-close",
)
),
type='link',
style={
"font-size": "20px",
"padding": '8px'
}
)
],
id="aichat_operate",
style={
"top": "15px",
"right": "8px",
"position": "absolute",
"display": "flex",
"z-index": "90000",
"align-items": "center"
}
),
dcc.Store(id="ai_chat_width", data="412px")
],
id="ai_chat_container",
style={
"z-index": "90000",
"border-radius": "8px",
"border": "1px solid #ffffff",
"background": "linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%,"
" rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1",
"box-shadow": "0px 4px 8px 0px rgba(31, 35, 41, 0.10)",
"position": "fixed",
"bottom": "16px",
"right": "16px",
"overflow": "auto",
"width": "450px",
"height": "600px",
"display": "none"
}
),
dcc.Store(id="current_g6"),
html.Div(
[
fac.AntdIcon(
id="g6-exit-full-screen",
icon="antd-full-screen-exit",
style={"float": "right",
"font-size": "25px",
"cursor": "pointer"}
),
html.Div(
id="g6-full-screen-body",
style={"width": "100%", "height": "100%"}
)
],
id='g6-modal',
style={
"z-index": '99999',
"width": '100%',
"height": '100%',
"position": 'absolute',
"left": '0px',
"top": '0px',
"display": 'none'
}
),
# 辅助处理多输入 -> 存储接口返回token校验信息
render_store_container(),
# 重定向容器
html.Div(id='redirect-container'),
# 登录消息失效对话框提示
fac.AntdModal(
html.Div(
[
fac.AntdIcon(icon='fc-high-priority', style={'font-size': '28px'}),
fac.AntdText('用户信息已过期,请重新登录', style={'margin-left': '5px'}),
]
),
id='token-invalid-modal',
visible=False,
title='提示',
renderFooter=True,
centered=True
),
# 注入全局消息提示容器
html.Div(id='global-message-container'),
# 注入全局通知信息容器
html.Div(id='global-notification-container')
]
)
@app.callback(
Output("ai_chat_button", "style"),
Input('url-container', 'pathname')
)
def listen_path_show_aichat(pathname):
if pathname == '/login':
return {'display': 'none'}
elif pathname == '/forget':
return {'display': 'none'}
else:
return {'z-index': '90000',
'position': 'fixed',
'bottom': '30px',
'right': '0px',
'display': 'block',
'width': '64px',
'padding': '0px',
'height': '64px'}
@app.callback(
[Output('ai_chat_container', 'style', allow_duplicate=True),
Output('aichat_zoom-open', 'style', allow_duplicate=True),
Output('aichat_zoom-close', 'style', allow_duplicate=True),
Output('ai_chat_width', 'data', allow_duplicate=True),
Output('ai_chat_button', 'style', allow_duplicate=True),
Output('chat_session_id', 'data', allow_duplicate=True),
],
Input('ai_chat_button', 'nClicks'),
[State('ai_chat_container', 'style'),
State('aichat_zoom-open', 'style'),
State('aichat_zoom-close', 'style'),
State('ai_chat_button', 'style'),
State('chat_data_list', 'data')
],
prevent_initial_call=True
)
def click_ai_chat_button_event(nClicks, style, open_style, close_style, button_style, chat_data_list):
if nClicks:
style['display'] = 'block'
style['width'] = '450px'
style['height'] = '600px'
style['bottom'] = '16px'
style['right'] = '16px'
open_style['display'] = 'block'
close_style['display'] = 'none'
button_style['display'] = 'none'
if chat_data_list and len(chat_data_list) > 0:
return style, open_style, close_style, '412px', button_style, dash.no_update
else:
return style, open_style, close_style, '412px', button_style, uuid.uuid4()
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
@app.callback(
[Output('ai_chat_container', 'style', allow_duplicate=True),
Output('ai_chat_button', 'style', allow_duplicate=True)],
Input('close-chat-window', 'nClicks'),
[State('ai_chat_container', 'style'),
State('ai_chat_button', 'style')],
prevent_initial_call=True
)
def close_chat_window(nClicks, style, button_style):
if nClicks:
style['display'] = 'none'
button_style['display'] = 'block'
return style, button_style
return dash.no_update
@app.callback(
[Output('ai_chat_container', 'style', allow_duplicate=True),
Output('aichat_zoom-close', 'style'),
Output('aichat_zoom-open', 'style'),
Output('ai_chat_width', 'data', allow_duplicate=True)
],
Input('aichat_zoom-open', 'nClicks'),
[State('ai_chat_container', 'style'),
State('aichat_zoom-close', 'style'),
State('aichat_zoom-open', 'style')],
prevent_initial_call=True
)
def aichat_zoom_open_click(nClicks, style, close_style, open_style):
if nClicks:
close_style['display'] = 'block'
open_style['display'] = 'none'
style['width'] = '50%'
style['height'] = '100%'
style['bottom'] = '0px'
style['right'] = '0px'
return style, close_style, open_style, "calc(50vw - 48px)"
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
app.clientside_callback(
# 使用目标容器id为Input,利用初次自动回调触发即时渲染
ClientsideFunction(namespace="aichat_clientside", function_name="refresh_chat_div"),
Input('ai_chat_container', 'style'),
State("chat_data_list", "data"),
prevent_initial_call=True
)
@app.callback(
[Output('ai_chat_container', 'style', allow_duplicate=True),
Output('aichat_zoom-close', 'style', allow_duplicate=True),
Output('aichat_zoom-open', 'style', allow_duplicate=True),
Output('ai_chat_width', 'data', allow_duplicate=True)
],
Input('aichat_zoom-close', 'nClicks'),
[State('ai_chat_container', 'style'),
State('aichat_zoom-close', 'style'),
State('aichat_zoom-open', 'style')],
prevent_initial_call=True
)
def aichat_zoom_close_click(nClicks, style, close_style, open_style):
if nClicks:
close_style['display'] = 'none'
open_style['display'] = 'block'
style['display'] = 'block'
style['width'] = '450px'
style['height'] = '600px'
style['bottom'] = '16px'
style['right'] = '16px'
return style, close_style, open_style, '412px'
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
@app.callback(
output=dict(
app_mount=Output('app-mount', 'children'),
redirect_container=Output('redirect-container', 'children', allow_duplicate=True),
global_message_container=Output('global-message-container', 'children', allow_duplicate=True),
api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True),
menu_current_key=Output('current-key-container', 'data'),
menu_info=Output('menu-info-store-container', 'data'),
menu_list=Output('menu-list-store-container', 'data'),
search_panel_data=Output('search-panel', 'data')
),
inputs=dict(pathname=Input('url-container', 'pathname')),
state=dict(
url_trigger=State('url-container', 'trigger'),
session_token=State('token-container', 'data')
),
prevent_initial_call=True
)
def router(pathname, url_trigger, session_token):
"""
全局路由回调
"""
# 检查当前会话是否已经登录
token_result = session.get('Authorization')
# 若已登录
if token_result and session_token and token_result == session_token:
try:
current_user_result = get_current_user_info_api()
if current_user_result['code'] == 200:
current_user = current_user_result['data']
menu_list = current_user['menu']
user_menu_list = sorted([item for item in menu_list if item.get('visible') == '0'],
key=itemgetter('order_num'))
menu_info = deal_user_menu_info(0, menu_list)
user_menu_info = deal_user_menu_info(0, user_menu_list)
search_panel_data = get_search_panel_data(user_menu_list)
session['user_info'] = current_user['user']
session['dept_info'] = current_user['dept']
session['role_info'] = current_user['role']
session['post_info'] = current_user['post']
# session['newtest_info'] = current_user['newtest']
dynamic_valid_pathname_list = find_node_values(menu_info, 'href')
valid_href_list = dynamic_valid_pathname_list + RouterConfig.STATIC_VALID_PATHNAME
if pathname in valid_href_list:
current_key = find_key_by_href(menu_info, pathname)
if pathname == '/':
current_key = '首页'
if pathname == '/user/profile':
current_key = '个人资料'
if url_trigger == 'load':
# 根据pathname控制渲染行为
if pathname == '/login' or pathname == '/forget':
# 重定向到主页面
return dict(
app_mount=dash.no_update,
redirect_container=dcc.Location(pathname='/', id='router-redirect'),
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key={'current_key': current_key},
menu_info={'menu_info': menu_info},
menu_list={'menu_list': menu_list},
search_panel_data=search_panel_data
)
# 否则正常渲染主页面
return dict(
app_mount=views.layout.render_content(user_menu_info),
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key={'current_key': current_key},
menu_info={'menu_info': menu_info},
menu_list={'menu_list': menu_list},
search_panel_data=search_panel_data
)
else:
return dict(
app_mount=dash.no_update,
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key={'current_key': current_key},
menu_info={'menu_info': menu_info},
menu_list={'menu_list': menu_list},
search_panel_data=search_panel_data
)
else:
# 渲染404状态页
return dict(
app_mount=views.page_404.render_content(),
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
else:
return dict(
app_mount=dash.no_update,
redirect_container=dash.no_update,
global_message_container=dash.no_update,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
except Exception as e:
print(e)
return dict(
app_mount=dash.no_update,
redirect_container=None,
global_message_container=fuc.FefferyFancyNotification('接口异常', type='error', autoClose=2000),
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
else:
# 若未登录
# 根据pathname控制渲染行为
# 检验pathname合法性
if pathname not in RouterConfig.BASIC_VALID_PATHNAME:
# 渲染404状态页
return dict(
app_mount=views.page_404.render_content(),
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
if pathname == '/login':
return dict(
app_mount=views.login.render_content(),
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
if pathname == '/forget':
return dict(
app_mount=views.forget.render_forget_content(),
redirect_container=None,
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
# 否则重定向到登录页
return dict(
app_mount=dash.no_update,
redirect_container=dcc.Location(pathname='/login', id='router-redirect'),
global_message_container=None,
api_check_token_trigger={'timestamp': time.time()},
menu_current_key=dash.no_update,
menu_info=dash.no_update,
menu_list=dash.no_update,
search_panel_data=dash.no_update
)
if __name__ == '__main__':
app.run(host=AppConfig.app_host, port=AppConfig.app_port, debug=AppConfig.app_debug)