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)